今天该唠唠:
- 打手的自我安全修养(线程安全与线程不安全里面的那些东东、生产者与消费者模型)?
no… - 打手集团里面的那些西西?(线程池)
no… - 还是java内存模型(JMM)
no… - 还是唠唠一些鸡毛蒜皮的小概念:阻塞、等待、睡眠、挂起、同步、异步、唤醒、并发、并行…
no…
这些以后再聊,总会聊到的,2022年的某一天喽。今天先看看打手(线程)玩的兵器有哪些?看看打手平时都玩些什么呢。
开整~
话说这天呀,打手(线程)嚷嚷着要拉着敏小言和小胡夫妻俩去看看自己那天闯关的视频回放,小胡贼头贼脑的心里想,(不都已经被已经三个里屋关卡给挡住了嘛线程之打手的故事,这没闯过去有啥好聊的,难道看看录像易边再战嘛)。小胡实在不想去看视频,过年在家整天不是手机电脑就是电视,视频这玩意实在不想最近再刷了,所以说:哎呀,听说你哪天和里屋三个不同的对手大战了几百回合,你用的什么兵器呀,兵器上肯定千疮百孔了吧。
打手:哎哟,想不到虎兄竟然对兵器还有如此兴趣,快走走走,趁此时间带你夫妻俩去看看我的兵器库。
敏小言:老公,我就不去了,一会转其他地方我再去,我懒得跑了。记得转完去吃牛肉馅的馄饨,好久没吃了。
胡:收到,我们速转速回。
十分钟后,打手和小胡来到了打手的武器库。
打手便开始慢慢的说到,虎兄呀,我的兵器除了一些不常用的暗器、药品之类的,主要有两种是我经常外出带着用的。
- 近身短兵器:java.lang.Object 中的兵器们
- 远距离长兵器:java.lang.Thread中的兵器们
胡:哎呦,你这可以呀,快,继续继续谝~
打手:所谓近身的短兵器,你从名字就能听出来,这用的话是前提条件的呢。哎不过,老兄,说一句实话哪种兵器有没有自己的合适的使用场景呢,其实都有不同的使用条件罢了。
胡:确实也是这样。
打手:
java.lang.Object 中的东东就是下面这三样:
wait()、notify()、notifyall()
- obj .wait()让
进入object监视器的线程到waitSet等待
- obj .notify()在object上
正在waitSet等待的线程中挑一个唤醒
- obj . notifyAll()让
object.上正在waitSet等待的线程全部唤醒
不过呀,凡事都有两面,得先把先师留给我的武器使用注意事项给你看看:
这三个兵器有详细的说明书,你先翻翻看看。notify和wait是配合synchronized内置锁实现线程间同步的基础设施~线程间同步或者线程安全,看这篇
- 当一个线程**
调用一个共享变量的wait()方法
**时该线程会被阻塞挂起。相当于线程自己一旦调了这个共享变量的wait()方法后线程就昏睡过去了,(在调用共享变量的notify和wait方法前必须先获取该共享变量的内置锁)- 当线程调用共享变量(对象)的wait()方法时当前线程只会释放当前共享对象的锁,当前对象持有的其他共享对象的监视器锁并不会被释放
- 叫醒昏睡的线程的方法有下面两种方法:
- 其他线程调用了该共享对象(变量)的notify()或者notifyAll()方法
- 图中所示,其他线程调用这个昏睡线程的interrupt()方法,该线程抛出java.lang.InterruptedException异常返回。
- wait()方法API如下:
//使用Object中wait()这三个方法的正确姿势
synchronized(1ock) {
while(条件不成立) {
lock.wait();
}
...
}
//另一个线程.
synchronized(1ock) {
1ock.notifyA11();
}
- notify():一个线程调用共享对象的notify()方法后会唤醒一个在该共享对象或者叫共享变量上调用过wait()系列方法后被挂起的线程
- 被唤醒的线程不能马上从wait()方法返回并继续执行,他必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒他的线程释放了共享变量或者叫共享对象上的监视器锁之后,被唤醒的线程也需要和其他线程一块竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。(当前线程获取到了共享变量的监视器锁后才可以调用共享变量的notify()方法否则会抛出IllegalMonitorStateException异常)
- notifyAll()方法会唤醒所有在该共享变量上由于调用了wait()系列方法而被挂起的线程(在共享变量上调用notifyAll()方法后只会唤醒调用这个方法前调用了调用了wait()系列函数而被放入共享变量等待集合中的线程们,相当于就是调用了notifyAll()方法后一个线程才调用了该共享变量的wait()方法而被放入阻塞集合那这个线程是不会被notifyAll()唤醒的)
- 一个线程可以从挂起状态变为可运行状态(被唤醒状态),但是该线程没有被其他线程调用notify()、notifyAll()方法进行通知、或者被中断、或者等待超时,就产生了**
虚假唤醒
**。- 防范措施:(在一个循环中调用wait()方法进行防范)不停的测试该线程被唤醒的条件是否满足,不满足该线程则继续等待。当然,退出循环的条件是满足了唤醒该线程的条件。
synchronized(obj){//这个obj就是共享变量呀,不然咱给他加synchronized防止同步下的线程不安全干吗?
while(条件不满足){
obj.wait();//调用共享变量wait()方法。首先通过同步块获取obj上面的监视器锁,然后在while循环内调用obj的wait()方法
}
}
打手:另外一种兵器呢,所谓远距离长兵器,就是java.lang.Thread类下的兵器们,如下:
具体的就是:
- sleep():静态的 sleep() 方法是当一个
执行中的线程
调用了 Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 PU 的调度
,但是该线程所拥有的监视器资源,比如锁还是持有不让出的 。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与 CPU 的调度
,获取到 CPU 资源后就可以继续运行了。- 如果在睡眠期间其他线程调用了该线程的 interrupt()方法中断了该线程,则该线程会在调用 sleep() 方法的地方抛出 IntermptedException 异常而返回
- 如果在调用 Thread.sleep(long millis)时为 millis 参数传递了负数, 抛出 IllegalArgumentException 异常
- 🕴sleep() 方法和 wait() 方法区别和共同点?
两者都可以暂停线程的执行,
线程进入这俩时,状态是一样的,都是TIMED_WAITING- 区别:
- sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。
sleep()睡眠不释放锁(如果有的话)
。wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。wait()睡眠时会释放互斥锁
。 - sleep 通常被用于暂停执行Wait 通常被用于线程间交互/通信
- sleep() 方法执行完成后,线程
会自动苏醒
。或者可以使用 wait(long timeout)超时后线程会自动苏醒。wait() 方法被调用后,线程不会自动苏醒
,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法来唤醒线程 - sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法
- wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,
既然wait() 要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)
,所以wait()要定义在Object类中而不是在Thread中 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁
。所以sleep()方法定义在Thread中而不是Object类中
- wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,
- sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。
- yield():Yield方法(当一个线程调用 yield 方法时,实际就是在暗示线程调度器当前线程请求让出自己 CPU 使用,但是线程调度器可以无条件忽略这个暗示。)
可以暂停当前正在执行的线程对象,当前线程就会处于就绪状态
,让其它有相同优先级或者更高优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU(选到谁看CPU心情),执行yield()的线程有可能在进入到暂停状态后马上又被执行。
- 操作系统是为每个线程分配一个时间片来占有CPU 的,正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而**
当一个线程调用了 Thread 类的静态方法 yield 时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了
,这暗示线程调度器现在就可以进行下一轮的线程调度
** sleep()与yield()方法的区别
:
- 当线程调用 sleep 方法时**
调用线程会被阻塞挂起指定的时间
**,在这期间OS的线程调度器不会去
调度该线程,因为你被阻塞挂起了,你的状态是Timed Waiting状态咯,这我咋调 而调用yield()方法时线程只是让出自己剩余的时间片
,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行,也可能调不到,随机的谁也说不定
- TimeUnit:建议的Thread的sleep方法的替代品
- TimeUnit:建议的Thread的sleep方法的替代品
- 当线程调用 sleep 方法时**
- 操作系统是为每个线程分配一个时间片来占有CPU 的,正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而**
- start() && run()
- 启动一个线程必须用start(),run()方法只会狐假虎威,驴粪蛋蛋
- 【
可以直接调用 Thread 类的 run 方法吗
?】:------->new一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容
,这是真正的多线程工作。但是如果直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它
,所以这并不是多线程工作。【调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行】
- 【
- start方法调用之前,线程的状态是NEW;start方法调用之后线程的状态是RUNNABLE,懂了没
- start方法不能多次调用,调用多次会报错,报不合法的线程状态错
- 启动一个线程必须用start(),run()方法只会狐假虎威,驴粪蛋蛋
- isAlive()
- join():在项目实践中经常会遇到一个场景,
就是当需要等待某几件事情完成后(或者说一个线程等待另一个线程结束才能...)才能继续往下执行
,比如多个线程加载资源 需要等待多个线程全部加载完毕再汇总处理。 Thread 类中的join() 方法就可以做这个事情。sleep虽然也可以延时从而抹平时间差,但是有时候某个程序或者线程执行的时间不好测控或者把握,那你怎么抹平时间差。
- 某个线程,比如说main主线程同步等待多个线程的返回结果时,可以分别调用这多个线程的join()方法,就可以实现这种情况。
- 在这种情况下使用后面会讲到的
CountDownLatch
是个不错的选择,见CountDownLatch
- 某个线程,比如说main主线程同步等待多个线程的返回结果时,可以分别调用这多个线程的join()方法,就可以实现这种情况。
//join源码:
public final void join() throws InterruptedException {
join(millis: 0);//调用下面这个有参join
}
- setPriority(int newPriority):任务优先级仅仅是个提示,CPU不一定会管你这个优先级,只不过会有限考虑你而已,但不一定你真正的会先执行。
- 还有守护线程有关的方法如下:
- 守护线程:默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,
即使守护线程的代码没有执行完,也会强制结束
。垃圾回收器线程就是一种守护线程
- Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求
- 守护线程:默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,
- 线程的中断:通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理
- 当线程A运行时,线程B可以调用线程A的interrupt() 方法来设置线程的中断标志为 true并立即返回。(
设置标志仅仅是设置标志,线程A实际并没有被中断, 会继续往下执行
) 如果线程A因为调用了wait系列函数、 join方法或者 sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt() 方法,线程A会在调用这些方法的地方抛 InterruptedException异常而返回
。- interrupt()方法
- 打断调用了sleep()等阻塞的线程,会清空线程的中断标志
- 打断正常的线程不会清空线程的中断标志
- 在interrupted()内部是获取
当前调用线程的中断标志
而不是调用interrupted() 方法的实例对象的中断标志。- 判断当前线程是否被打断并返回true或者false之后,
会清除打断标记
- 判断当前线程是否被打断并返回true或者false之后,
- isInterrupted,
调用这个isInterrupted不会清除打断标记
- 当线程A运行时,线程B可以调用线程A的interrupt() 方法来设置线程的中断标志为 true并立即返回。(
- 当线程为了等待一些特定条件的到来时,一般会调用 sleep函数、wait 系列函数或者join()函数来阻塞挂起当前线程。(比如一个线程调用了 Thread.sleep(3000),那么调用线程会被阻塞直到3s后才会从阻塞状态变为激活状态)
- 但是有可能在 3s 内条件己被满足,如果一直等到 3s 再返回有点浪费时间,这时候可以调用该线程的 interrupt() 方法强制 sleep方法抛出InterruptedException 异常而返回,线程恢复到激活状态。
两阶段终止模式
:在一个线程T1中如何"优雅’终止线程T2?这里的[优雅]指的是给T2一个料理后事的机会。
- 1.错误思路
- 使用线程对象的stoip()方法停止线程
stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
- 使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但这种做法会让整个程序都停止
- 使用线程对象的stoip()方法停止线程
- 2.两阶段终止模式:
//两阶段终止模式的实现方式一:
class TwoPhaseTermination {
private Thread monitor;
//启动监控线程
public void start() {
monitor = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if(current. isInterrupted()) {
Log.debug("料理后事");
break;
}
try {
Thread.sleep(millis: 1000); //情况1
log.debug(“执行监控记录"); //情况2
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置打断标记
current.interrupt();
}
}
});
monitor.start();
}
//停止监控线程
public void stop() {
monitor.interrupt();
}
打手:好啦,这些武器,各有各的用处和使用条件,所以你用的时候才知道厉害不厉害。
胡:哎,那你这个还有没有啥暗器呀啥的,无影针呀啥的,给我一个,我以后能防防身,哎,哎,人呢
打手已经走出门外,向食堂而去…
最后:
巨人的肩膀:
Java并发编程之美
深入理解Java虚拟机
B站各位老师