多线程的学习
1、Callable和runnable区别:
class c implements Callable<String>{
@Override
public String call() throws Exception {
return null;
}
}
class r implements Runnable{
@Override
public void run() {
}
}
相同点:
1、两者都是接口
2、两者都需要调用Thread.start启动线程
不同点:
1、如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值
2、call方法可以抛出异常,但是run方法不行
3、因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常
4、callable和runnable都可以应用于executors。而thread类只支持runnable
测试:使用线程池来运行
public static void main(String[] args) throws Exception{
//1 创建一个线程池
//调用Executors类的静态方法
ExecutorService service = Executors.newFixedThreadPool(10);
//2提交runnable对象
service.submit(new Runnable() {
@Override
public void run() {
}
});
//3 提交callable对象
service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return null;
}
});
//4 关闭线程池
service.shutdown();
}
使用Thread来创建一个Callable,调用
Tread的构造方法中没有Tread(Callable callable)方法,所以需要一个既和Runable有关系,又与Callable有关系的类,FutureTask类。
FutureTask实现了Runable接口,并且其构造方法可以传递Callable
public static void main(String[] args) throws Exception{
// callable用thread调用需要一个FutureTask中间类
Runnable target;
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return null;
}
};
FutureTask futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
// 获取返回值
Object o = futureTask.get();
}
做个简单的小测试:
- 有4个同学,1同学计算1+2+3+4,2同学计算1+2+…+10000,3同学计算20+21+22,4同学计算100+200+300。
- 这时同学2的计算量比较大,FutureTask单开启线程给2同学计算,先汇总1,3,4同学的计算数据,等2同学完成后再调用get汇总全部。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+System.currentTimeMillis()+"进入了callable中");
int result = 0;
for (int i = 0 ;i<10001;i++){
result += i;
}
System.out.println(Thread.currentThread().getName()+System.currentTimeMillis()+"结束");
return result;
}
});
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("第二位同学的计算值:"+futureTask.get());
int a = 1+2+3+4;
int b = 20+21+22;
int c = 100+200+300;
int all = a+b+c+(int)futureTask.get();
System.out.println("所有的计算值:"+all);
}
2、线程池基本方法了解:
1、基本了解
java.util.concurrent包中的ExecutorService。ExecutorService就是Java中对线程池的实现
Java API对ExecutorService接口的实现有两个,所以这两个即是Java线程池具体实现类
1. ThreadPoolExecutor
2. ScheduledThreadPoolExecutor
除此之外,ExecutorService还继承了Executor
接口(注意区分Executor接口和Executors工厂类),这个接口只有一个execute()
方法,最后我们看一下整个继承树:
2、ExecutorService的创建
创建一个什么样的ExecutorService的实例(即线程池)需要根据具体应用场景而定,不过Java给我们提供了一个Executors工厂类
,它可以帮助我们很方便的创建各种类型ExecutorService线程池,Executors一共可以创建下面这四类线程池:
1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutor
、ScheduledThreadPoolExecutor
这两个类的实例。
3、基本使用方法
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
4、ExecutorService的执行
ExecutorService有如下几个执行方法:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
4.1 execute(Runnable)
-------接收一个runnable实例,犹豫runnable缘故,是没有返回值的,且是异步执行
这个方法接收一个Runnable实例,并且异步的执行,请看下面的实例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例(下面会介绍)。
4.2 submit(Runnable)
submit 和execute的区别:
1)submit 可以传入runnable或者是callable,execute只能传入runnable
2)由于runnable 和callable区别,会导致,是否有任务的结果
submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕,请看下面执行的例子:
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get(); //returns null if the task has finished correctly.
如果任务执行完成,future.get()方法会返回一个null。注意,future.get()方法会产生阻塞。
4.3 submit(Callable)
submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果,而Runnable接口中的run()方法是void的,没有返回值。请看下面实例:
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
如果任务执行完成,future.get()方法会返回Callable任务的执行结果。注意,future.get()方法会产生阻塞。
4.4 invokeAny(…)
invokeAny(…)方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。请看下面实例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
大家可以尝试执行上面代码,每次执行都会返回一个结果,并且返回的结果是变化的,可能会返回“Task2”也可是“Task1”或者其它。
4.5 invokeAll(…)
invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。情况下面这个实例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();
5、ExecutorService的关闭
当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。
举个例子,如果的应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。
如果要关闭ExecutorService中执行的线程,我们可以调用ExecutorService.shutdown()方法。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。
如果我们想立即关闭ExecutorService,我们可以调用**ExecutorService.shutdownNow()**方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
3、ScheduledExecutorService用法
创建一个线程池,主要用来,定时,或者周期行的执行某些任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("延迟三秒");
}
},3, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟1秒,每3秒钟执行一次");
}
},1,3,TimeUnit.SECONDS);
}
4、线程的生命周期
线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。 在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自 运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
1、new新建状态 :线程被创建之后,就处于新建状态,此时只由JVM来给其分配内存,并初始化其变量值
2、runnable就绪状态:当线程对象调用了start方法后,线程就进入就绪状态,JVM为其创建方法,调用栈和程序计数器,等待执行;
3、running运行状态,当在就绪状态的线程获得了cpu的使用权之后,此时开始执行run方法的中的具体代码了,此时线程处于运行状态;
4、blocked阻塞状态:阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice(时间片),暂时停止运行。 直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状 态。
阻塞的情况分三种:
等待阻塞(o.wait->等待对列): 运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue) 中。
同步阻塞(lock->锁池) 运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线 程放入锁池(lock pool)中。
其他阻塞(sleep/join) 运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态。
5、线程死亡(DEAD) 线程会以下面三种方式结束,结束后就是死亡状态。
正常结束 1. run()或 call()方法执行完成,线程正常结束。
异常结束 2. 线程抛出一个未捕获的 Exception 或 Error。
调用 stop 3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QfBQW3YP-1645523308700)(E:\笔记\image-20220222163705954.png)]
5、终止线程的4种方式
5.1、程序正常运行结束,线程自动结束
5.2、使用标志位来终止线程
般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的 运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如: 最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while 循环是否退出,代码示例:
package com.example.ThreadStudy;
/**
* 通过标志位退出线程
*
* @author LZH
* @version 1.0
* @date 2022/02/22 17:09:00
*/
public class ThreadTest1 extends Thread{
public volatile boolean isExit = true;
@Override
public void run(){
while (isExit){
System.out.println("通过标志位退出线程");
}
}
}
义了一个退出标志 exit,当 exit 为 false时,while 循环退出,exit 的默认值为 true.在定义 exit 时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只 能由一个线程来修改 exit 的值。
java中volatile关键字的含义
在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。
Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。
synchronized (代码块)
同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用
synchronized 修饰的方法 或者 代码块。
volatile(变量)
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
5.3、使用Interrupt 方法结束线程
1、程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时, 会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。 阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让 我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实 际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正 常结束 run 方法。
2、线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用 interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理
isInterrupted 默认是false ,如果try catch 中的线程阻塞的话,会抛出异常,结束本次线程,
package com.example.ThreadStudy;
/**
* 通过interrupt方法退出线程
*
* @author LZH
* @version 1.0
* @date 2022/02/22 17:09:00
*/
public class ThreadTest2 extends Thread{
@Override
public void run(){
while (!isInterrupted()){ //不是阻塞状态的话,通过isInterrupted返回值来判断
try {
Thread.sleep(6 * 300);//阻塞状态的话,通过异常的捕获,来break当前的循环
} catch (InterruptedException e) {
e.printStackTrace();
break;//退出循环
}
}
}
}
5.4 使用stop 方法终止线程(线程不安全)
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关 闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是: thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子 线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈 现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因 此,并不推荐使用 stop 方法来终止线程。
6、sleep 和wait 的区别
sleep 和 wait 区别
1、sleep 是 Thread 类的方法,wait 是object 类的方法
2、sleep 睡眠的时候是不会释放对象锁的,wait释放对象锁,并且进入等待池中,等再次获取到锁
3、sleep 睡眠之后,时间到了之后,自动回复运行状态
7、start和run方法的区别
1、Thread调用start 是线程启动的方法,调用start之后,线程进入了runnable就绪状态,不用管run方法是否执行,可以执行下面的方法
2、run方法执行之后,线程进入运行状态
3、run方法中是线程体,执行完成之后,线程结束,cpu调用其他的线程