敞开成长之旅!这是我参加「日新计划 12 月更文应战」的第5天,点击查看活动详情
学习MOOC视频记录的笔记
回忆:
完结多线程通常有两种办法,本质上是一种办法,外在形式有多种办法
应该用start()办法发动线程而不是run()办法
中止线程实际上是一种规范,不仅需求咱们让它中止,还需求被中止的线程配合咱们
线程一生的6种状况记住图就好了
1.为什么线程通讯的办法 wait()
,notify()
和notifyAll()
被界说在 Object
类里?而 sleep
界说在 Thread
类里?
2.用 3
种办法完结生产者形式
3.Java SE8
和 Java1.8
和 JDK8
是什么联系,是同一个东西吗?
4.join
和 sleep
和wait
期间线程的状况分别是什么?为什么?
Thread
和Object
类中的重要办法详解
1.办法概览
类 | 办法名 | 介绍 |
---|---|---|
Thread |
sleep 相关 |
本表格的相关,指的的重载办法,也便是办法名相同,可是参数不同,例如 sleep 有多个办法,只是参数不同,实际效果大同小异 |
join |
等候其他线程履行结束 | |
yield 相关 |
抛弃现已获取到的 CPU 资源 |
|
currentThread |
获取当时履行线程的引证 | |
start,run 相关 |
发动线程相关 | |
interrupt 相关 |
中止线程 | |
stop(),suspend(),resume() 相关 |
已抛弃 | |
Object |
wait/notify/notifyAll 相关 |
让线程暂时歇息和唤醒 |
2.wait, notify, notifyAll
办法详解
2.1 效果、用法
能够操控一些线程的歇息与唤醒
堵塞阶段
想让一个或多个线程去歇息一下,后续需求它或许条件成熟的时分再去唤醒它。履行 wait
办法的时分必须具有目标的Monitor
锁。调用者进入堵塞状况,不再被调度。
唤醒阶段
直到以下4种状况之一发生时,才会被唤醒
- 另一个线程调用这个目标的
notify()
办法且刚好被唤醒的是本线程; - 另一个线程调用这个目标的
notifyAll()
办法; - 过了
wait(long timeout)
规则的超时时刻,如果传入0
便是永久等候; - 线程自身调用了
interrupt()
【相似之前在sleep
的时分调用interrupt
会抛出反常】
遇到中止
特殊状况,线程现已履行了 wait
办法,在此期间被中止了,会抛出 InterruptException
办法,而且开释掉现在现已取得的 Monitor
nofity()
会唤醒单个正在等候某目标 Monitor
目标的线程,唤醒的时分如果有多个线程在等候则会随机唤醒一个,详细的完结是交给 JVM
来说完结的。
wait()
和 notify()
必须在 synchronized
代码块中履行,不然会抛出反常。
2.2 代码演示:4种状况
一般用法
/**
* 展现wait和notify的根本用法
* 1. 研究代码履行次序
* 2. 证明wait开释锁
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
// 获取同步监视器
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开端履行了");
try {
// 开释锁
object.wait();
// 如果在等候期间遇到了中止,需求处理反常
} catch (InterruptedException e) {
e.printStackTrace();
}
// 重新取得了锁
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
// 此刻还没有开释锁,同步代码块里边一切句子履行结束之后才会开释
System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
}
}
}
public static void main(String[] args) throws InterruptedException {
// 确保wait()先履行,notify()后履行
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
运转成果:
Thread-0开端履行了
线程Thread-1调用了notify()
线程Thread-0获取到了锁。
notify
和 notifyAll
展现:
/**
* 3个线程,线程1和线程2首要被堵塞,线程3唤醒它们。notify, notifyAll。
* start先履行不代表线程先发动。
*/
public class WaitNotifyAll implements Runnable {
public static final Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r, "ThreadA");
Thread threadB = new Thread(r, "ThreadB");
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
System.out.println("ThreadC notified.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName() + " waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出如下:
ThreadA got resourceA lock.
ThreadA waits to start.
ThreadB got resourceA lock.
ThreadB waits to start.
ThreadC notified.
ThreadB's waiting to end.
ThreadA's waiting to end.
如果运用 notify()
而不是 notifyAll()
ThreadA got resourceA lock.
ThreadA waits to start.
ThreadB got resourceA lock.
ThreadB waits to start.
ThreadC notified.
ThreadA's waiting to end.
可见只要 ThreadA
被唤醒了,而且程序永远不会结束。
如果注释掉 Thread.sleep(200);
运转成果如下:
ThreadA got resourceA lock.
ThreadA waits to start.
ThreadC notified.
ThreadB got resourceA lock.
ThreadB waits to start.
ThreadA's waiting to end.
线程 C
唤醒了堵塞的线程 A
,可是之后拿到锁的线程 B
就无法取得锁了。
只开释当时 monitor
展现
履行 wait
办法便是一个目标,哪一个目标履行 wait
就会开释这个目标对应的锁,而不会影响到其他的锁,锁与锁之间的行为是独立的。
/**
* 证明wait只开释当时的那把锁
*/
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
// 验证thread1是否还持有resourceB锁
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("ThreadB got resourceA lock.");
System.out.println("ThreadB tries to resourceB lock.");
synchronized (resourceB) {
System.out.println("ThreadB got resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
运转成果:
ThreadA got resourceA lock.
ThreadA got resourceB lock.
ThreadA releases resourceA lock.
ThreadB got resourceA lock.
ThreadB tries to resourceB lock.
2.3 特点、性质
- 用必须先具有
monitor
也即synchronized
锁 - 只能唤醒其间一个
- 归于
Object
类【任何目标都能够调用这些办法】 - 相似功用的
Condition
- 一起持有多个锁的状况
2.4 原理
- 进口集
Entry Set
- 等候集
Wait Set
2.5 留意点
从 Object.wait()
状况刚被唤醒时,通常不能马上抢到 monitor
锁,那就会从 Waiting
先进入 Blocked
状况抢到锁后再转换到 Runnable
状况(官方文档)
线程被唤醒之后一般是拿不到锁的,即进入 Block
状况;(即由 Waiting
状况到 Block
状况)
如果发生反常,能够直接跳到停止 Terminated
状况,不用再遵从途径,比方能够从 Waiting
直接到 Terminated
。
手写生产者顾客规划形式
为什么要运用生产者和顾客形式?
生产者和顾客解耦
生产者向行列中添加,如果加满了则进入堵塞,如果生产了(行列不空)就能够通知顾客来取了。
顾客从行列中取出,如果取完了则进入堵塞,如果取出了(行列不满)就能够通知生产者消费了。
代码:
/**
* 用wait/notify来完结生产者顾客形式
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
/**
* 资源类
*/
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
this.maxSize = 10;
this.storage = new LinkedList<>();
}
public synchronized void put() {
// 如果满了就等候
while(storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不然没满就加入并通知
storage.add(new Date());
System.out.println("库房里有了" + storage.size() + "个产品。");
notify();
}
public synchronized void take() {
while(storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + ", 现在库房还剩余" + storage.size());
notify();
}
}
2.6 常见面试问题
两个线程交替打印
0~100
的奇偶数偶线程:0 奇线程:1 偶线程:2
根本思路: synchronized
有许多的废操作。比方偶数线程先拿到锁,count++ = 1
,持续履行 while
循环,偶数线程或许持续拿到锁,此刻不满足 count & 1 == 0
的条件,持续循环,或许会有许多不用要的履行,直到奇数线程拿到锁并履行 count++
。
/**
* 两个线程交替打印0~100的奇偶数,用synchronized关键字完结
*/
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
// 新建2个线程
// 第一个只处理偶数,第二个只处理奇数(用位运算)
// 用synchronized来通讯
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(count < 100) {
synchronized (lock) {
// 运用位运算进步功率
if((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "偶数").start();
new Thread(new Runnable() {
@Override
public void run() {
while(count < 100) {
synchronized (lock) {
// 运用位运算进步功率
if((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "奇数").start();
}
}
更好的办法:wait()/notify()
功率更高
/**
* 两个线程交替打印0~100的奇偶数,用wait/notify完结
*/
public class WaitNotifyPrintOddEvenWait {
private static int count = 0;
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new TurningRunner(), "偶数").start();
// 让偶数线程先与奇数线程发动
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new TurningRunner(), "奇数").start();
}
// 1. 拿到锁,咱们就打印
// 2. 打印完,唤醒其他线程,自己就休眠
static class TurningRunner implements Runnable {
@Override
public void run() {
while(count <= 100) {
synchronized (lock) {
// 拿到锁就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
// 唤醒其他线程
lock.notify();
if(count <= 100) {
try {
// 如果使命还没结束,就让出当时的锁,并休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
用程序完结两个线程交替打印 0~100 的奇偶数
手写生产者顾客规划形式
为什么
wait()
需求在同步代码块内运用,而sleep()
不需求为了安全,主要是让通讯变得牢靠,避免死锁或许永久等候的发生。由于如果不把
wait()
和notify()
都放到代码块里边的话,或许会导致先履行notify()
再履行wait()
。需求相互配合的操作都放到同步代码块里边了。
为什么线程通讯的办法
wait()
,notify()
和notifyAll()
被界说在Object
类里?而sleep
界说在Thread
类里?锁等级的操作,锁是绑定到目标中,不是绑定在线程中。
Java
目标规划的是每个目标都是一把锁。
wait
办法是Object
归于目标的,那调用Thread.wait
会怎样样?Thread 也是一个目标,能够做,线程退出的时分会自动履行notify,会使得整个流程被影响,不引荐这样写
怎样选择用
notify
仍是nofityAll
?考虑是需求唤醒多个线程仍是一个线程
notifyAll
之后一切的线程都会再次抢夺锁,如果某线程抢夺失败怎样办?回到初始状况,堕入等候状况,等候持有者开释,直到拿到锁持续下一步操作
用
suspend
和resume
来堵塞线程能够吗?为什么?由于安全问题现已被弃用了,引荐运用
wait
和ntotify
来完结。
彩蛋:Java相关概念
JavaSE
,JavaEE
,JavaME
是什么?
SE是标准版,EE是企业版,ME是移动版,现在都是SE
JRE
和 JDK
和 JVM
是什么联系?
JRE是Java运转时环境,是JDK的一部分;JDK是开发工具包,用于开发;JVM是JRE的一部分,JRE还有一些其他的类库
Java
版别晋级都包括了哪些东西的晋级?
类的晋级以及JVM的晋级
Java8
和 Java1.8
和 JDK8
是什么联系,是同一个东西吗?
最开端版别是1.1、1.2、1.3…,Java5。Java 8便是Java SE8。一般认为是同一个东西。
3.sleep
办法详解
效果:我只想让线程在预期的时刻履行,其他时分不要占用 CPU
资源
不开释锁,包括 synchronized
和 lock
,和 wait
不同
/**
* 展现线程sleep的时分不开释synchronized的monitor,等sleep时刻到了以后,正常结束后才开释锁
*/
public class SleepDontReleaseMonitor implements Runnable {
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
}
}
运转成果:
线程Thread-0获取到了monitor。
线程Thread-0退出了同步代码块
线程Thread-1获取到了monitor。
线程Thread-1退出了同步代码块
/**
* 演示sleep不开释lock(lock需求手动开释)
*/
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
try {
Thread.sleep(5000);
System.out.println("线程" + Thread.currentThread().getName() + "现已复苏");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
运转成果:
线程Thread-0获取到了锁
线程Thread-0现已复苏
线程Thread-1获取到了锁
线程Thread-1现已复苏
sleep
办法响应中止
-
抛出
InterruptedException
- 铲除中止状况
第二种写法(更高雅)
/**
* 每个1秒钟输出当时时刻,被中止,调查。
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
*/
public class SleepInterrupted implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
// 1. 不需求转化时刻
// TimeUnit.HOURS.sleep()
// 2. 如果传参小于0,忽略处理
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中止了!");
e.printStackTrace();
}
}
}
}
运转成果:
Mon Mar 01 20:31:54 CST 2021
Mon Mar 01 20:31:55 CST 2021
Mon Mar 01 20:31:56 CST 2021
Mon Mar 01 20:31:57 CST 2021
Mon Mar 01 20:31:58 CST 2021
Mon Mar 01 20:31:59 CST 2021
Mon Mar 01 20:32:00 CST 2021
我被中止了!
Mon Mar 01 20:32:01 CST 2021
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:27)
at java.lang.Thread.run(Thread.java:748)
Mon Mar 01 20:32:02 CST 2021
Mon Mar 01 20:32:03 CST 2021
一句话总结:
sleep
办法能够让线程进入Waiting
状况,而且不占用CPU
资源,可是不开释锁,直到规则时刻后再履行,休眠期间如果被中止,会抛出反常并铲除中止状况
wait/notify、sleep
异同(办法归于哪个目标?线程状况怎样切换?)相同:①都会让线程进入堵塞状况;②能够响应中止
不同:①前者必须在同步办法中履行,后者不需求;②前者开释锁,后者不开释锁;③前者不指定时刻,等候被唤醒。后者指定时刻,到时刻就会被唤醒;④前者在
Object
类中,后者在Thread
类中。
4.join
办法
4.1 效果
效果:由于新的线程加入了咱们,所以咱们要等他履行完再出发
用法:main
等候 thread
履行结束,留意谁等谁
4.2 用法
4.3 三个例子
/**
* 演示join,留意句子输出次序,会变化。
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "履行结束");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "履行结束");
}
});
thread1.start();
thread2.start();
System.out.println("开端等候子线程运转结束");
thread1.join();
thread2.join();
// 由于join的生效,因而线程的输出句子一定在下面这条句子之前完结
System.out.println("一切子线程履行结束");
}
}
运转成果:
开端等候子线程运转结束
Thread-1履行结束
Thread-0履行结束
一切子线程履行结束
如果注释掉 join
句子
运转成果:
开端等候子线程运转结束
一切子线程履行结束
Thread-1履行结束
Thread-0履行结束
关于中止的状况:
/**
* 演示join期间被中止的效果
*/
public class JoinInterrupt {
public static void main(String[] args) {
// 获取主线程的引证
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 中止主线程,此刻主线程现已给子线程让位了
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished");
} catch (InterruptedException e) {
System.out.println("子线程中止");
// e.printStackTrace();
}
}
});
// 发动子线程
thread.start();
System.out.println("等候子线程运转结束");
// 子线程来插队了,实际上是主线程被中止,主线程抛出反常
try {
thread.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主线程中止了");
// 主线程被中止的时分必须还要中止子线程,不然会出现不一致的状况
thread.interrupt();
// e.printStackTrace();
}
System.out.println("子线程现已运转结束");
}
}
等候子线程运转结束
main主线程中止了
子线程现已运转结束
子线程中止
获取状况:
/**
* 先join再mainThread.getState(),经过debugger看线程join前后状况的比照
*/
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(mainThread.getState());
System.out.println("Thread-0运转结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等候子线程运转结束");
thread.join();
System.out.println("子线程运转结束");
}
}
运转成果:
等候子线程运转结束
WAITING
Thread-0运转结束
子线程运转结束
4.4 CountDownLatch
或 CyclicBarrier
尽量不要直接操作底层的办法,运用更高档的封装好的类库。
4.5原理
源码
public final void join() throws InterruptedException {
// 休眠时刻无限
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
// 一向休眠
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
这里只要 wait(0)
,那么究竟是被谁唤醒的呢?
分析
等价
/**
* 经过解说join原理,分析出join的替代写法
*/
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("Thread0 finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("开端等候子线程运转结束");
// thread.join();
// 主线程拿到锁
synchronized (thread) {
thread.wait();
}
System.out.println("一切子线程履行结束");
}
}
开端等候子线程运转结束
Thread0 finished
一切子线程履行结束
4.6常见面试问题
在 join
期间,线程处于哪种线程状况?
线程会处于 WAITING
的状况
5.yield
办法
效果:开释我的 CPU
时刻片 –> Runnable状况,有或许下一段又被履行了
定位:JVM
不确保遵从
yield
和 sleep
差异:是否随时或许再次被调度
6.获取当时履行线程的引证: Thread.currentThread
办法
/**
* 演示打印main,Thread-0,Thread-1
*/
public class CurrentThread implements Runnable {
public static void main(String[] args) {
new CurrentThread().run();
new Thread(new CurrentThread()).start();
new Thread(new CurrentThread()).start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}