多线程核心知识总结(五)——趣解Thread和Object类中的线程相关方法

    科技2022-08-04  92

    多线程核心知识总结

    趣解Thread和Object类中的线程相关方法

    方法概览

    wait,notify,notifyAll 的作用和方法

    阻塞阶段

    四种情况下会被唤醒

    另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程。另一个线程调用这个对象的notifyAll()过了wait(long timeout)规定的超时时间,如果传入0就是永久等待。线程自身调用了interrupt()

    唤醒阶段

    notify会唤醒单个正在等待某对象monitor的线程,如果有多个线程都在等待,它只会唤醒一个,具体唤醒的选择是任意的,java对此没有明确规范,JVM可以拥有自己的实现,对此有一定的自由裁量权,而notify和wait都得在synchronize保护的代码块或者方法中执行

    /** * 描述: 展示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 { Thread1 thread1 = new Thread1(); Thread2 thread2 = new Thread2(); thread1.start(); Thread.sleep(200); thread2.start(); } } /** * 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 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(); } } } } /** 1. 描述: 证明wait只释放当前的那把锁 */ 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 等待集

    生产者消费者设计模式

    /** * 描述: 用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() { 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

    /** * 描述: 两个线程交替打印0~100的奇偶数,用synchronized关键字实现 */ 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减少废操作

    /** * 描述: 两个线程交替打印0~100的奇偶数,用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不同 /** 1. 展示线程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() + "退出了同步代码块"); } }

    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 { 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执行完毕,注意谁等谁普通用法: /** * 描述: 演示join,注意语句输出顺序,会变化。 */ 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的替代方法

    /** * 描述: 通过了解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); } 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只是暂时将调度权让出
    Processed: 0.053, SQL: 8