Java多线程:创建多线程的"四种" 方式
每博一文案
白马笑西风写道:江南有杨柳,有燕子,金鱼......汉人中有的是英俊勇武的少年,倜傥潇洒的少年......
但这个美丽的姑娘就像故高昌国人那样固执:"那都是很好很好的,可是我偏不喜欢"。
谁都被这样义无反顾的选择吧,不是畏首畏尾,不是瞻前顾后,更不是权衡利弊的审视,而是下意识的惦记和牵挂,
是义无反顾的选择。这个世界上没有人有义务堆你好,所以你更应该,这样相信哪些冷漠和刻薄是理所当然的,
哪些温柔相待更应该珍惜,如果总是相信下一个会更好。那么,往往遇见的会一个比一糟糕,经历了越多人就是
容易拿来对比,也就越来越不相信真心。
所以,不要因为一件东西好,你才千方百计去拥有他,而是因为你已经拥有了它,才一心一意觉得
他最好,听过这样一句话:你若不懂得珍惜,我又何必牵挂,人缘之间的关系是流动的?
时间总会拆穿一些,秘而不宣的事实,那就是没有人会一直在原地等你。
攒够失望的人,自然会放手,一颗心就像茶壶中开水,需要不断加火升温,才滚烫,你若对我认真,我就还你情深。
你若对我沉默,我就对你冷漠,我知道,我不是最好的那一个,却也是谁也代替不了的人,与人相遇一场。何其有辛,愿你
珍惜。
—————— 一禅心灵庙语
文章目录
- Java多线程:创建多线程的"四种" 方式
- 每博一文案
- 一. 基本概念
- 1.1. 程序
- 1.2. 进程
- 1.3. 线程
- 1.4. 进程 与 线程 的关系
- 二. 多线程的理解
- 2.1. 单核CPU 和 多核 CPU的区别
- 2.2. 并行 和 并发
- 2.3. 同步编程 和 异步编程
- 2.4. 多线程的好处
- 2.5. 何时需要多线程
- 三. 创建多线程的 "四" 种方式 及其 “启动” 多线程
- 3.1.创建多线程方式一: extends Thread
- 3.1.1 JDK 中的 Jconsole工具查看线程
- 3.1.2 使用匿名子类,实现创建 Thread 子类的方式创建多线程
- 3.2. 创建多线程方式二: implements Runnable
- 3.2.1 使用匿名实现类,创建实现 Runnable 接口
- 3.3 创建多线程方式三:implements Callable JDK5.0 新增的方式
- 3.4. 线程池
- 3.4.1 线程池相关APl 标准库中ThreadPoolExecuter构造方法()
- 3.4.2 线程池的 ”执行流程“ 和 ”拒绝策略“
- 3.4.3 线程池的优点
- 四. 关于多线程创建的面试题:
- 4.1 Thread 类 和 Runnable 创建多线程的异同点
- 4.2 实现 Callable 接口的方式 与 实现 Runnable 接口的异同
- 五. 总结:
- 六. 最后:
一. 基本概念
1.1. 程序
程序(program) : 是为完成特定任务,用某种语言编写的一组指令的集合。即便是指 一段静态的代码
,静态对象,运行在计算机内存当中。
1.2. 进程
进程(process) :具有执行独立的执行环境。一个进程通常有一套完整的,私有的基本运行时资源,特别地,每个进程都有自己内存空间,进程通常被视为程序或应用程序的代名词。然而,用户将其视为单个应用程序可能实际上时一组协作过程
。
进程简单的说就是一个类似:于运行 中的QQ,运行 中的网易音乐。是 程序 的一次执行过程,或是正在运行的一个程序
。 是一个动态的过程:有它自身的产生,存在和消亡的过程。
程序是下载好的静态的,进程是运行的动态的 。
一个进程有占有一个JVM中的 方法区
和 堆区空间
。
进程作为资源分配的基本单位
,系统运行时会为每个进程分配不同的内存区域的空间,让其执行。
如我们:点击 ——> 电脑上的 ‘任务管理器’ 。就可以简单的查看,我们电脑上正在运行的 进程了。如下
1.3. 线程
线程(thread) : 有时被称为 ”轻量级进程“。进程和线程都提供了一个执行环境,但创建一个新的线程 比 创建一个新的线程要少得多的资源的消耗。
将进程 进一步细化就是多个线程的组成,是一个程序 内部的一条执行的路径。所以简单的说:一个进程中可以包含多个线程,一个进程至少有一个线程存在。
若一个进程同一时间并行 执行多个线程,就是支持多线程的。
线程作为调度和执行的单位,每个线程拥有独立的运行栈
和程序计数器(pc)
,所以每个线程之间相互独立(压栈弹栈),互不干扰,多线程可以同时运行,互不影响,各自执行各自的。线程切换的开销小。
一个线程中多个线程共享相同的内存单元 / 内存地址空间。它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便,高效。 但多个线程操作共享的资源数据,可能存在 多线程的同步安全问题 。
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程 。
1.4. 进程 与 线程 的关系
举一个例子:用来解释进程 与 线程的关系:
将阿里巴巴公司比作是一个进程:而马云是阿里巴巴公司中的一个线程,以及现在的 阿里 CTO 吴泽明也是一个线程,阿里巴巴所有的员工都是一个线程。
将京东公司比作是一个进程:而其中的刘强东就是一个线程,京东中所有员工也是一个线程。
在Java当中:一个进程:占用一个方法区 和 一个堆区,一个进程可以包含多个线程。而一个线程占用:一个栈区 和 一个程序计数器PC。
一个进程包含多个线程:同一个进程中的数据是被多个线程共享的,进程之间的数据是不共享的,就像 阿里巴巴公司的数据是不会和京东公司的数据共享的。一个线程一个栈:线程的数据是不共享的。启动10个线程,会有10个栈空间,每个栈和每个栈之间(每个线程和每个线程之间),互不干扰,各自执行各自的,这就是多线程并发。
如下是 JVM 对线程和进程关系的内存图:
堆(Heap) : 存放 new 对象的实例,几乎所有的对象的实例都在这里分配,
栈(Stack)(虚拟机栈) : 存放局部变量,方法执行完释放该变量所占空间(压栈弹栈)。
方法区(Method Area) : 存放 static 静态变量,静态方法,常量,类加载信息,静态代码块。
二. 多线程的理解
**背景:**计算机用户认为他们的系统一次可以做一件以上的事情。他们假设他们可以继续在文字处理器中工作, 而其他应用程序下载文件,管理打印队列和流音频。通常,一个单一的应用程序通常会一次做一件事情。 例如,流式音频应用程序必须同时从网络上读取数字音频,解压缩,管理播放和更新其显示。 即使文字处理器也应该随时准备好响应键盘和鼠标事件,无论重新格式化文本或更新显示器多么忙碌。 可以做这些事情的软件被称为 并发软件。
一个线程就是一个执行流(按一定顺序执行的一组代码),每个线程之间都可以按照顺序执行自己的代码,多个线程之间 ”同时“执行多份代码,同一个线程中的这些线程,共用同一个进程中的(方法区 和 堆区)。
进程时资源分配的基本单位,线程是调度执行的基本单位 。
举例理解:
火车站的人工售票窗口:
三个人工售票窗口:窗口1,窗口2,窗口3
我去第一个窗口,你去地二个窗口,我买我的火车票,你买你的火车票,你不用等我,我不用等你。这样的售票的效率就提高了。
所以Java之所以有多线程机制:就是为了提高程序的处理业务的效率。
2.1. 单核CPU 和 多核 CPU的区别
首先我们先来思考一个问题:单核CPU 是可以 ”真正“ 的现实 多线程的吗 ???
答:是不可以的,但是可以模拟实现一种错误的 多线程 ,主要:利用的就是 CPU 的高频处理,以及时间片的轮转。
例如:现在有 两个线程: t1 线程 和 t2线程,需要单核CPU 处理执行,但是一个单核CPU 在同一个时间点是只能处理一件事的,
所以单核CPU 就这么做:一段时间处理t1线程的处理(但是t1这个线程的事务没有完成,可能就仅仅完成了 1/3 ),就停下来,去执行t2线程的事务了t2线程处理完了一段时间(t2线程的事务也没有处理完,可能处理完了 1/2),就跑去继续处理 t1线程没有处理完的事务了。
… 这些循环往复,直到所有t1线程,t2线程的事务都处理完了,单核CPU才休息。由于 单核CPU的太高频了(轮转处理线程太快了),让用户在使用上(一种单核CPU 也可以实现多线程的处理)的错觉。
就像我们 70/80 年代的 看的一个电影: 一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动态的。
而多核CPU 就是真正的多线程处理了,因为有多核CPU,所以可以同时间,让多个CPU 同时处理不同线程的事务。
2.2. 并行 和 并发
并行: 就是多
个CPU 同一时间,同时
处理多个任务。比如多个人做同时做不同的事情
并发: 就是单
CPU 采用时间片的方式,多个线程同时处理个同一件事情:比如 多个用户同时抢夺一张火车票,电商的秒杀。
2.3. 同步编程 和 异步编程
举例:处理两个线程的事务:分别是 t1线程,t2线程。
同步编程: CPU正在处理t1线程的事务,这时候的 t2线程不能被执行处理,只有等到t1线程处理完事务了,才能处理t2线程的事务,同步编程就是排队。
异步编程: CPU正在处理t1线程的事务,t2线程也可以被另外一个CPU处理,t2线程的处理不需要等待t1线程处理结束,t1线程也不需要等待t2线程的处理,各自处理各自的不需要等待对方。其实就是并发编程(提高效率)。
2.4. 多线程的好处
- 多线程并发编程:可以更充分利用 多核CPU 的多核的特点,提高效率。
- 其次是,多进程 虽然也可以实现并发编程,但是 线程 比 进程 更轻量(无论是在 创建上,销毁上,调度上 线程都比进程更快)。
- 销毁线程,比销毁进程的代价更小,一个线程的数据的销毁,不会影响到其它的线程,线程之间是相互独立了,但是销毁进程 ,其中所有线程共享(方法区和堆区)中的数据可都没了。
- 提高应用程序的响应,用户的体验,如(程序加载资源:文本的加载比图片的加载更快,这就就可以用多线程处理,一个线程加载图片,一个线程加载文本,这样就不至于文本和图片都出不来了)。
- 改善程序结构,将既长又复杂的进程又分为多个线程,独立运行,利于理解和修改(如项目的模块化)。
2.5. 何时需要多线程
- 程序需要同时执行两个或多个任务(Java:mian主线程,gc垃圾回收线程,异常处理线程)。
- 程序需要实现等待的任务时,如:用户输入,文件读写,网络操作,搜索
- 需要一些后台的程序时。
三. 创建多线程的 “四” 种方式 及其 “启动” 多线程
在Java当中有 ”四“种方式创建多线程。
3.1.创建多线程方式一: extends Thread
Thread ( ) 的构成器
Thread(); // 创建一个新的线程对象。
Thread(String name); // 创建一个新的线程对象,并设置其线程名
Thread(Runnable target); // 创建新线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name); // 创建一个新线程它实现了Runnable接口中的run方法,并设置线程名
创建多线程的步骤
- 创建 extends Thread 的子类。
class MyThread extends Thread {
}
- 重写Thread 中的 run()方法。
@Override
public void run() {
if (target != null) {
target.run();
}
}
重写的如下:
@Override
public void run() {
for(int i = 0; i < 10;i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread() // 静态方法,方法当前线程的引用
// Thread.currentThread().getName() // 返回当前线程名
}
}
- 创建 extends Thread 的子类的实例对象
// 方式一: 直接 new 对象
MyThread myThread = new MyThread();
// 方式二: 多态性: 动态绑定
Thread thread = new MyThread();
- 调用 Thread 中的 start()方法:创建新线程,和启动调用run()的方法
myThread.start();
完成创建多线程实例:
package blogs.blog4;
public class ThreadTest1 {
public static void main(String[] args) {
// 3. 创建 extends Thread 的子类的实例对象
MyThread myThread = new MyThread();
Thread thread = new MyThread(); // 多态性: 动态绑定
// 4. 调用Thread中的 start()方法,创建新线程,并启动run()方法
myThread.start();
// 主线程执行的任务。
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
// 1. 创建继承 Thread 的类
class MyThread extends Thread {
// 2. 重写 Thread 类中的 run()方法:
// run()方法就是该分支线程要执行代码,处理的业务
@Override
public void run() {
for(int i = 0; i < 10;i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread() // 静态方法,方法当前线程的引用
// Thread.currentThread().getName() // 返回当前线程名
}
}
}
从运行结果上看,可以看到两个线程再 交替的执行
就说明我们是多线程创建成功了,并执行了。
为什么是线程会交替执行,有执行多的,也执行少的 ???
答:是因为多线程的执行,是需要抢夺CPU时间片(CPU的执行权),拿到了CPU的时间片才可以执行对应的线程的。
所以出现了线程的交替执行:一会主线程抢到了CPU时间片,下一秒被分支线程抢到了。
有多有少:是因为一个线程多次抢到了CPU的时间片,执行了线程中的程序。
具体的内容:大家可以移步至:🔜🔜🔜
3.1.1 JDK 中的 Jconsole工具查看线程
在我们安装的 JDK 中有一个名为 Jconsole 的工具,可以供我们查看在Java当中运行的多线程。
首先我们先编写一个创建多线程的 java程序:
创建两个线程:一个主main线程打印 100 以内的 偶数,另外一个分线程打印 100 0以内的 奇数。
需要注意的一点就是,如果想让 Jconsole 工具可以查看到我们运行的线程,则该线程需要进入休眠(阻塞)状态(可以使用Thread.sleep()方法让当前线程进入休眠(阻塞状态),该方法的具体信息大家可以移步至:🔜🔜🔜 ) 。不然,线程运行结束了,Jconsole 是无法查看到线程的执行的。
package blogs.blog4;
public class ThreadTest1 {
public static void main(String[] args) {
// 3. 创建 extends Thread 的子类的实例对象
MyThread myThread = new MyThread();
Thread thread = new MyThread(); // 多态性: 动态绑定
// 4. 调用Thread中的 start()方法,创建新线程,并启动run()方法
myThread.start();
// main 主线程执行的任务。
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try { // 当前线程进入 休眠(阻塞)状态,让 Jconsole 可以查看到当前线程
Thread.sleep(1000 * 10); // 当前main主线程休眠 10s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
}
// 1. 创建继承 Thread 的类
class MyThread extends Thread {
// 2. 重写 Thread 类中的 run()方法:
// run()方法就是该分支线程要执行代码,处理的业务
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
try { // 当前线程进入 休眠(阻塞)状态,让 Jconsole 可以查看到当前线程
Thread.sleep(1000* 10); // 让当前线程 休眠 10s ,sleep()静态方法让当前线程休眠 s,单位毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread() // 静态方法,方法当前线程的引用
// Thread.currentThread().getName() // 返回当前线程名
}
}
}
}
- 打开 Jconsole 工具两种方式:方式一: 找到我们安装的 JDK 的目录,找到其中的 bin 目录。打开 bin 目录,从中找到一个名为
jconsole.exe
的可执行程序,双击打开就可以了。
第二种打开:jconsole 的方式,使用 DOS 命令窗口,因为我们已经将 JDK 配置到 我们的电脑的环境变量当中去了,其中JDK的文件,我们可以直接通过 DOS命令的方式打开。如下:
首先 按住 win + R 键 ,输入 cmd 打开 DOS 命令窗口,输入 jconsole
就可以了。如下
-
运行我们创建多线程的程序,就是我们上面哪个打印 奇偶数的程序,再打开 Jconsole ,再本地进程中找到我们所运行的 main 方法的类名称,如下
-
双击打开该进程,点击 不安全的连接,因为你也没有别的选择了。
注意当,我们的线程运行结束了,该进程连接也就断了,就无法查看了 。
3.1.2 使用匿名子类,实现创建 Thread 子类的方式创建多线程
对于一些简单的多线程处理,我们可以使用 匿名子类的方式 继承 Thread 完成一个线程的任务。减少代码量。
对于上述:两个线程打印奇偶数的程序,改用 匿名子类的方式实现。如下:
public class ThreadTest1 {
public static void main(String[] args) {
Thread t1 = new Thread(){ // 多态性
// 重写其中的乱方法
@Override
public void run() {
// 打印 100 以内的 偶数
for(int i = 1; i < 100; i++) {
if( i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
};
// 创建多线程,启动run()方法
t1.start();
Thread t2 = new Thread() {
@Override
// 打印奇数
public void run() {
for(int i = 1; i < 100; i++) {
if(i % 2 != 0) { // Thread.currentThread().getName()返回当前线程名
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
};
t2.start();
}
}
多线程编程:
线程之间是相互独立的,各自运行各自的,各自执行各自的(压栈弹栈),互不影响,当一个线程停止了,其它线程仍然会继续执行
如下:我们在 main 主线程中定义一个 int num = 2 / 0
这个算数异常,让主线程中止,而分支线程继续。
package blogs.blog4;
public class ThreadTest1 {
public static void main(String[] args) {
Thread thread = new MyThread(); // 多态性
// 分支线程的创建,启动run()方法
thread.start();
// main主线程,int num = 2/0; 算数异常,线程中止
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
int num = 2 / 0; // 算数异常中止
}
}
}
// 1. 创建继承 Thread 的类
class MyThread extends Thread {
// 2. 重写 Thread 类中的 run()方法:
// run()方法就是该分支线程要执行代码,处理的业务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread() // 静态方法,方法当前线程的引用
// Thread.currentThread().getName() // 返回当前线程名
}
}
}
3.2. 创建多线程方式二: implements Runnable
介绍一下 Runnable 是一个接口
Runnable 接口的源码如下:
package java.lang;
/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* This interface is designed to provide a common protocol for objects that
* wish to execute code while they are active. For example,
* <code>Runnable</code> is implemented by class <code>Thread</code>.
* Being active simply means that a thread has been started and has not
* yet been stopped.
* <p>
* In addition, <code>Runnable</code> provides the means for a class to be
* active while not subclassing <code>Thread</code>. A class that implements
* <code>Runnable</code> can run without subclassing <code>Thread</code>
* by instantiating a <code>Thread</code> instance and passing itself in
* as the target. In most cases, the <code>Runnable</code> interface should
* be used if you are only planning to override the <code>run()</code>
* method and no other <code>Thread</code> methods.
* This is important because classes should not be subclassed
* unless the programmer intends on modifying or enhancing the fundamental
* behavior of the class.
*
* @author Arthur van Hoff
* @see java.lang.Thread
* @see java.util.concurrent.Callable
* @since JDK1.0
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
其中在 Runnable 接口当中只有一个 run() 的抽象方法
我们在回到 Thread 类当中,可以看到是 implements 实现了 Runnable 接口的,所以需要重写 接口 Runnable 中的 run()
抽象方法的
public
class Thread implements Runnable {
Thread 重写的 run( ) 方法
private Runnable target; // Thread 中定义的Runnable 类型
@Override
public void run() {
if (target != null) {
target.run(); // 调用的其实就是 Runnable 接口中的 run()方法
}
}
还记得 Thread类 中有一个的构造器,拥有参数有 Runnable 的吗???,使用这个构造器创建线程。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
从 Thread 类 和 Runnable 接口的源码分析,我们可以看出:Thread 中的 run()方法实际调用的就是 Runnable 接口中的 run()方法,所以方式一 extends Thread 类创建多线程时,重写的 run()方法其实重写的是 Runnable 接口中的 run()方法 。
这里我们使用 implements Runnable 接口 ,Thread(Runnable target) 的构造器创建新的 Thread 对象
步骤 :
- 创建 Runnable 接口的实现类
class MyRunnable implements Runnable {
- 重写 Runnable 接口中的 run() 抽象方法
@Override
public void run() {
// 实现线程要处理的业务
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程名
}
}
- 创建实现 Runnable 接口的实现类的实例对象
MyRunnable myRunnable = new MyRunnable();
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中,创建新的 Thread 对象
Thread thread = new Thread(myRunnable);
可以将第 3 步和 第 4 步结合起来
Thread thread2 = new Thread(new MyRunnable());
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
thread.start();
完整创建多线程的方式
package blogs.blog4;
public class ThreadTest2 {
public static void main(String[] args) {
// 3. 创建 实现 Runnable 接口的实现类的实例对象
MyRunnable myRunnable = new MyRunnable();
// 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
Thread thread = new Thread(myRunnable);
// 或者 3,4步结合使用
Thread thread2 = new Thread(new MyRunnable());
// 5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
thread.start();
// main主线程
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程名
}
}
}
// 1. 创建 Runnable 的实现类
class MyRunnable implements Runnable {
// 2. 重写 Runnable 中的 run()抽象方法
@Override
public void run() {
// 实现线程要处理的业务
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程名
}
}
}
3.2.1 使用匿名实现类,创建实现 Runnable 接口
对于一些简单的线程处理,可以使用匿名实现类,调用 Thread(Runnable target) 创建新的Thread对象
两个线程:一个线程处理100以内的偶数,一个线程处理100以内的奇数。
package blogs.blog4;
public class ThreadTest2 {
public static void main(String[] args) {
// 创建匿名实现类 Runnable
Thread t1 = new Thread(new Runnable() {
@Override
// 打印 100 以内的偶数
public void run() {
for(int i = 1; i <= 100 ; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程的名称
}
}
});
// 创建匿名实现类 Runnable
Thread t2 = new Thread(new Runnable() {
@Override
// 打印 100 以内的奇数
public void run() {
for(int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程的名称
}
}
});
// 调用Thread.start()方法创建新线程,启动重写Runnable 抽象方法的 run()方法
t1.start();
t2.start();
}
}
// 1. 创建 Runnable 的实现类
class MyRunnable implements Runnable {
// 2. 重写 Runnable 中的 run()抽象方法
@Override
public void run() {
// 实现线程要处理的业务
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程名
}
}
}
3.3 创建多线程方式三:implements Callable JDK5.0 新增的方式
了解一下:Callable 接口
Callable 接口的源码
package java.util.concurrent;
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
从源码上分析,其实和 Runnable 是一样的,只有一个抽象方法,稍微不同的是 Runnable 接口中的run()抽象方法没有返回值,而这里Callable的 call()有返回值。
步骤:
- 创建 Callable 接口的实现类。
class MyCallable implements Callable {
}
- 重写 Callable 接口中的 call()重写方法,其 call()方法的作用和 Runnable 接口中的 run()方法的作用是一样的:线程处理的业务,不同的一点就是,call()方法有返回值,run()方法没有返回值。
@Override
public Object call() {
int sum = 0;
for(int i = 0; i <= 100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "--->" + i);
// Thread.currentThread().getName()返回当前线程名
}
return sum; // 返回值: 自动装箱
}
- 创建 Callable 实现类的实例对象
MyCallable myCallable = new MyCallable();
- 将此Callable的实例对象作为传递到 FutureTask 构造器中,创建 FutureTask 的实例对象
FutureTask futureTask = new FutureTask(myCallable);
FutureTask 的介绍:
FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值
其实我们要使用的是 Future
这个接口,只是接口不能 new ,所以就只能使用 实现了 Future 这个接口的类了 ——> FutureTask 类了,
调用 FutureTask(Callable<v> callable)构造器 创建 FutureTask 的对象
FutureTask futureTask = new FutureTask(myCallable);
- 将 FutureTask 对象作为参数传递到 Thread(Runnable target) 构造器中创建 Thread对象
Thread thread = new Thread(futureTask);
Thread(Runnable target) 构造器,其中的 FutureTask 因为实现了 Runnable 接口所以可以作为 Runnable 类型的参数使用多态。
public class FutureTask<V> implements RunnableFuture<V> {
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
可以将 第 3 步,第 4 步和第 5步结合起来
Thread thread2 = new Thread(new FutureTask(new MyCallable()));
- 调用其中的Thread对象中的 start()方法:创建新线程,启动其中的call()方法
thread.start();
- 通过 FutureTask中的 get()对象方法, 获取到 Callable 中的 calll() 方法的 返回值 。当然这一步,也可以省略不写,我不想要获取 call()的返回值。
try {
Object object = futureTask.get(); // 获取到 call()方法的返回值
System.out.println("总和: " + object); // 自动拆箱
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
完整的实现流程如下:
package blogs.blog4;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建多线程的方式三: implements Callable
*/
public class ThreadTest3 {
public static void main(String[] args) {
// 3. 创建Callable 接口的实现类的实例对象
MyCallable myCallable = new MyCallable();
// 4.将此Callable的实例对象作为传递到 FutureTask构造器中,创建FutureTask的实例对象
FutureTask futureTask = new FutureTask(myCallable);
// 5. 将 FutureTask 对象作为参数传递到 Thread(Runnable target) 构造器中创建 Thread对象
Thread thread = new Thread(futureTask);
// 可以将 第 3 步,第 4 步和第 5步结合起来
Thread thread2 = new Thread(new FutureTask(new MyCallable()));
// 6. 调用其中的Thread对象中的 start()方法:创建新线程,启动其中的call()方法
thread.start();
// main()主线程
for(int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
// 7. 通过FutureTask 中的 get() 对象方法获取到 call()方法的返回值
try {
Object object = futureTask.get(); // 获取到 call()方法的返回值
System.out.println("总和: " + object); // 自动拆箱
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// 1. 创建 Callable 的实现类
class MyCallable implements Callable {
// 2. 重写 Callable 的抽象方法 call() 和 Runnable 中的 run()方法是一样的作用,不同的是 call()有返回值处理
@Override
public Object call() {
int sum = 0;
for(int i = 0; i <= 100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "--->" + i);
// Thread.currentThread().getName()返回当前线程名
}
return sum; // 返回值: 自动装箱
}
}
3.4. 线程池
了解
大多数执行器实现 java.util.concurrent
使用的线程池,由工作线程组成。这种线程从单独存在 Runnable
和 Callable
它执行,通常用于执行多个任务的任务。
使用工作线程可以最大限度地减少由于线程创建而导致的开销。线程对象使用大量内存,而在大规模应用程序中,分配和释放许多线程对象会创建显着的内存管理开销。
一种常见类型的线程池是固定线程池。这种类型的池总是具有指定数量的线程运行; 如果一个线程在某种程度上仍然在使用中被终止,它将被一个新的线程自动替换。任务通过内部队列提交到池中,每当有比线程更多的活动任务时,它将保存额外的任务。
固定线程池的一个重要优点是使用它的应用程序正常地降级。要了解这一点,请考虑一个 Web 服务器应用程序,其中每个 HTTP 请求由单独的线程处理。如果应用程序只是为每个新的 HTTP 请求创建一个新的线程,并且系统接收到的请求超过它可以立即处理的请求,那么当所有这些线程的开销超过系统的容量时,应用程序将突然停止对所有请求的响应。对于可以创建的线程数量的限制,应用程序将不会尽快为 HTTP 请求提供服务,但是它将尽可能快地对系统进行维护。
背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池当中,需要使用线程时,直接去线程池中获取,使用完线程后,放回线程池底当中去,可以避免频繁创建销毁,实现重复利用。
步骤:
- 创建一个可重用的线程池,使用
Executors.newFixedThreadPool()
静态方法返回一个线程池,用 ExecutorService 接口接收。
ExecutorService executorService = Executors.newFixedThreadPool(10);
- 创建一个实现 Runnable 接口的实现类 / Callable 接口的实现类
class MyRunnable2 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
// 2. 创建一个实现 Callable 接口的实现类
class MyCallable2 implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0; i < 10; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
return sum;
}
}
- 将创建的线程类Runnable/Callable实现类的对象交给线程池,让线程池中的线程来复杂执行
需要注意的是: 对于 Runnable 接口的线程任务使用 executorService.execute()
的对象方法,该方法没有返回值而对于 Callable接口的线程任务使用 executorService.submit()
的对象方法,可以接受Callable 中的 call()的返回值。
executorService.execute(new MyRunnable2()); // 用于 Runnable 接口类型的
Object object = executorService.submit(new MyCallable2()); // 用于 Callable 接口类型的有返回值
- 使用完了线程池,需要关闭线程池 使用
executorService.shutdown()
对象方法关闭线程池。
executorService.shutdown();
完整的实现方式如下:
package blogs.blog4;
import java.util.concurrent.*;
/**
* 创建多线程的第四种方式:线程池
*/
public class ThreadTest4 {
public static void main(String[] args) {
// 1. 创建一个可重用固定的线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 3. 将创建的线程类Runnable/Callable实现类的对象交给线程池,让线程池中的线程来复杂执行
executorService.execute(new MyRunnable2()); // 用于 Runnable 接口类型的
Future future = executorService.submit(new MyCallable2());// 用于 Callable 接口类型的有返回值
try {
System.out.println("总值: " + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 4. 使用完线程池,关闭线程池
executorService.shutdown();
}
}
// 2. 创建一个实现 Runnable 接口的实现类 / Callable 接口的实现类
class MyRunnable2 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
// 2. 创建一个实现 Callable 接口的实现类
class MyCallable2 implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0; i < 10; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
return sum;
}
}
3.4.1 线程池相关APl 标准库中ThreadPoolExecuter构造方法()
支持很多参数,支持很多选项,可以创建出不同风格的线程池
/**
* 设置线程池的属性值
* @param args
*/
public static void main(String[] args) {
// 1. 创建可重用固定的数量的线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10); // 10个可重用的线程的线程池
// 2.创建设置线程池的对象
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
/*
如下 ThreadPoolExecutor 的继承关系
public class ThreadPoolExecutor extends AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
如下 ExecutorService 的继承关系
public interface ExecutorService extends Executor
*/
// 设置线程池的核心池的大小
threadPoolExecutor.setCorePoolSize(15);
// 设置线程池的最大线程数
threadPoolExecutor.setMaximumPoolSize(20);
// 设置线程在终止之前可能保持空闲的时间限制。
// 如果存在超过当前在池中的线程核心数量,则在等待这段时间而不处理任务之后,多余的线程将被终止。 这将覆盖在构造函数中设置的任何值。
threadPoolExecutor.setKeepAliveTime(1000,null);
}
3.4.2 线程池的 ”执行流程“ 和 ”拒绝策略“
线程池的执行流程:
- 先判断当前线程数是否大于核心线程数,如果结果为 不大于 true,则新建线程并执行任务。
- 如果结果为 true ,则判断任务队列是否已满,如果结果为 已经满 false,就把任务添加到任务队列中等待线程执行。
- 如果结果为true,则判断当前线程数量是否超过最大线程数,如果结果为没有超过 false ,则新建线程执行此任务。
- 如果结果为 false,则执行拒绝策略。
拒绝策略 :
AbortPoliy : 中止策略,线程池会抛出异常并中止执行此次任务。
CallerRunPolicy:把任务交给此次任务 main 线程来执行。
DiscardPolicy:忽略新任务,先执行旧任务。
DiscardOldestPolicy:忽略最早的任务,执行新加入的任务。
3.4.3 线程池的优点
- 降低资源的消耗:通过重复利用线程池中的线程,不需要每次每次都创建:当需要执行任务的时候,不需要创建线程了,而是直接从线程池中取一个现成的线程直接使用,用完以后也不用销毁线程,而是归还给线程池。(如果没有线程池的话,创建一个线程就需要有额外的销毁线程的开销,如果频繁的创建线程,紧跟着的是线程销毁的开销也就不能忽略了)。
- 提高响应数量:(省去了创建线程这一步,所以当拿到任务时,就可以立即执行了)。
- 便于管理线程:我们可以对线程池进程一些设置的属性:比如定时,延时来执行某些线程,也可以控制最大并发线程数等功能
四. 关于多线程创建的面试题:
4.1 Thread 类 和 Runnable 创建多线程的异同点
- Thread 是类,而Runnable 是接口。
- Thread 本身就实现了 Runnable 接口。
public class Thread implements Runnable {
}
-
某某线程 implements 实现了某个接口的功能 比 某某线程继承了 Thread 类,更符合寓意,生活实际。
-
由于Java是单继承的关系:如果是 extends Thread 类的方式创建线程,就无法再继承其他的类了,存在局限性,而 Runnable 接口可以实现多个接口,因此 Runnable 的方式 扩展性更好。
-
Runnable 还可以用于 “资源的共享”,即,多线程都是基于 某一个 Runnable 对象建立的,它们会共享 Runnable 实例对象上的资源。通常使用的都是 Runnable 的方式创建多线程
4.2 实现 Callable 接口的方式 与 实现 Runnable 接口的异同
- 实现 Callable 接口 与 实现 Runnable 接口创建多线程的方式基本上时一样的。
- 不同的是 Callable 接口中的 call()方法有返回值,可以抛异常 Exception (编译异常)所以可以自定义异常,支持泛型的返回,而Runnable 接口中的 run() 方法没有返回值,不可以抛出异常,更不支持泛型的返回。
- Callable 线程执行时,需要等待 IO 的读取的返回值,这里线程会处于阻塞状态,等待读取到返回值,才会恢复到 就绪状态,效率上没有 Runnable 快,因为 Runnable 不用处理这些。
五. 总结:
- 一个进程占用一个方法区(类的加载,静态方法/变量,常量,静态代码块) 和 一个堆区(类对象)。一个进程包含多个进程,一个进程至少一个线程。进程与进程之间是相互独立的。
- 一个线程占用一个栈和一个程序计数器PC,同一个进程中的方法区和堆区中的数据是所有线程共享的。线程与线程之间相互独立,各自执行各自的(压栈弹栈),互不影响。比如:main线程停止了,分支线程并不会停止而是继续执行。
- 并行:多个线程处理同时处理多个任务,并发:多个线程同时处理一个任务:火车抢票,电商秒杀。
- 单核CPU 的 “模拟”多线程处理的时间片原理
- 多线程的好处。
- 创建多线程的 “四种方式”: 1. extends Thread, 2. implement Runnable 接口 ,3. implement Callable 接口,4. 创建线程池获取到线程池中的线程。
- JDK 中的 Jconsole 工具查看线程的使用。
- extends Thread 中的匿名继承子类的,implement Runnable 接口中的匿名实现类
// extends Thread 中的匿名继承子类的
Thread t1 = new Thread(){ // 多态性
// 重写其中的乱方法
@Override
public void run() {
// 打印 100 以内的 偶数
for(int i = 1; i < 100; i++) {
if( i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
};
// 创建匿名实现类 Runnable
Thread t1 = new Thread(new Runnable() {
@Override
// 打印 100 以内的偶数
public void run() {
for(int i = 1; i <= 100 ; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// Thread.currentThread().getName() 返回当前线程的名称
}
}
});
-
Thread 与 Runnable 的异同。
-
Runnable 与 Callable 的异同。
-
线程池的好处。
六. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见 !!!