多线程核心知识总结
趣解Thread和Object类中的线程相关方法
方法概览
wait,notify,notifyAll 的作用和方法
阻塞阶段
四种情况下会被唤醒
- 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程。
- 另一个线程调用这个对象的notifyAll()
- 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待。
- 线程自身调用了interrupt()
唤醒阶段
notify会唤醒单个正在等待某对象monitor的线程,如果有多个线程都在等待,它只会唤醒一个,具体唤醒的选择是任意的,java对此没有明确规范,JVM可以拥有自己的实现,对此有一定的自由裁量权,而notify和wait都得在synchronize保护的代码块或者方法中执行
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 {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
// resourceA.notify();
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();
}
}
}
}
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
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();
}
}
wait notify,notifyAll的特点和性质
- 用必须先拥有monitor
- 只能唤醒其中一个
- 属于Object类
- 类似功能的Condition
- 同时持有多个锁的情况 :只会释放现在找到个wait()对应对象的那把锁。
wait原理
Entry Set 入口集
Wait Set 等待集
生产者消费者设计模式
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() {
maxSize = 10;
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();
}
}
wait,notify常见面试问题
用程序实现两个线程交替打印0~100的奇偶数
基本思路 synchronize
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
//新建2个线程
//1个只处理偶数,第二个只处理奇数(用位运算)
//用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减少废操作
public class WaitNotifyPrintOddEveWait {
private static int count = 0;
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new TurningRunner(), "偶数").start();
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();
}
}
}
}
}
}
}
手写生产者消费者设计模式
上文已写
为什么wait()需要在同步代码块使用而sleep()不需要
为了让通信变得可靠,防止死锁和永久等待的发生,因为如果我们不把wait和notify都放在代码块里面的话,那么很有可能是执行wait之前,线程突然切换,切换到具有notify的一个线程,因为没有synchronize保护,随时都可以切过去,这样对面的第二个线程就吧程序执行完毕了,会导致进入wait()后,没有notify能唤醒,就会永久等待或者死锁,而sleep不存在这样的问题。
为什么线程通信的方法wait(),notify(),notifyAll()被定义在Object里面,而sleep定义在Thread里面
因为wait(),notify(),notifyAll(),是锁级别的操作,而锁是属于某个对象的,所以这三个方法被定义在Object里面。
wait方法是属于Object对象的,那调用Thread.wait()会怎么样?
Thread也是继承Object的,但是对于Thread类很特殊,线程退出时会自动调用notify(),这样设计的整个流程都会受到影响。
notifyAll后,所有线程都去再次抢夺锁,如果某线程抢夺失败会如何
没有抢到锁的线程会进行等待,直到拿到锁。
sleep方法详解
作用:让线程在预期的时间执行,其他时候不要占用CPU资源
sleep方法不释放锁:
- 包括synchrinized 和 lock
- 和wait不同
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() + "退出了同步代码块");
}
}
sleep方法响应中断
- 抛出InterruptedException
- 清除中断状态
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 {
TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(25);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中断了!");
e.printStackTrace();
}
}
}
}
sleep方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。
sleep常见面试问题
sleep,wait/nofity异同(方法属于哪个对象?线程状态怎么切换?)
-
相同
阻塞:都会让线程进入阻塞状态
响应中断:即使休眠期间也会响应中断,抛出异常 -
不同
同步方法中:wait和notify必须在同步方法中执行(线程安全,防止死锁和永久等待),sleep不需要
释放锁: wait会释放锁,sleep不会
指定时间:sleep必须传参,wait如果不传参会直到自己被唤醒。
所属类
join 方法详解
- 作用:因为新的线程加入了我们,所以我们要等他执行完再出发
- 用法: main等待thread1执行完毕,注意谁等谁
- 普通用法:
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread = 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() + "执行完毕");
}
});
thread.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
thread.join();
thread2.join();
System.out.println("所有子线程执行完毕");
}
}
join注意点
CountDownLatch 或CyclicBarrier类
相同效果的实现类
join原理
源码:
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;
}
}
}
写出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);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
thread.start();
System.out.println("开始等待子线程运行完毕");
thread.join();
// synchronized (thread) {
// thread.wait();
// }
System.out.println("所有子线程执行完毕");
}
}
join常见面试问题
在join期间,线程处于哪种线程状态?
join期间线程会处于waiting的状态
yield方法详解
- 作用:释放我的CPU时间片
- 定位:JVM不保证遵循
- yield和sleep区别:是否随时可能再次被调度 sleep期间被阻塞,yield只是暂时将调度权让出