wait、notify、notifyAll、sleep、join、yield详解

一、wait、notify、notifyAll

wait、notify、notifyAll都要求当前线程拥有该object的monitor锁
wait、notify、notifyAll、sleep、join、yield详解_第1张图片

1.1 wait-notify演示

//创建俩线程,一个wait之后,另一个notify唤醒
public class Wait {
    public final static Object object = new Object();
    static class Thread1 extends Thread{
        @Override
        public void run(){
            synchronized (object){
                System.out.println("线程"+Thread.currentThread().getName()+"开始执行了");
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"进入wait");
                    object.wait();	//wait执行后,就会释放synchronization锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"重新获取了锁");

            }
        }
    }

    static class Thread2 extends Thread{
        @Override
        public void run(){
            synchronized (object){
                object.notify();	//Thread2获取到synchronization锁后,唤醒wait
                System.out.println("线程"+Thread.currentThread().getName()+"调用了notify()");
            }
        }
    }

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

1.2 wait-notifyAll演示

//创建三个线程,俩wait,另一个用notifyAll唤醒
public class WaitNotifyAll implements Runnable{
    private static final Object resourceA = new Object();
    public static void main(String[] args) {
        Runnable runnable = new WaitNotifyAll();
        Thread threadA = new Thread(runnable);
        Thread threadB = new Thread(runnable);
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    resourceA.notifyAll();	//唤醒所有线程
                    System.out.println("ThreadC notified.");
                }
            }
        });
        threadA.start();
        threadB.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadC.start();
    }

    @Override
    public void run() {
        synchronized (resourceA){
            System.out.println(Thread.currentThread().getName()+" get resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName()+" wait to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+" is going to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

若这里使用notify代替notifyAll唤醒线程,则最后的结果是随即唤醒一个线程。

1.3 用两个线程交替打印0-100的奇偶数

1.3.1 synchronization实现

public class WaitNotifyPrintOddEvenSyn {
    private static int count;
    private static final Object lock=new Object();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(count<100){
                    
                    //随机的竞争锁,如果拿到锁,就执行if语句
                    synchronized (lock){
                        if ((count & 1)==0){    //二进制相与(num & 1)即(num & 000···0001)
                            System.out.println(Thread.currentThread().getName()+" count:"+count);
                            count++;
                        }else {
                            System.out.println("偶数,资源浪费");
                        }
                    }
                    ///
                }
            }
        },"偶数").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(count<100){
                    
                    //随机的竞争锁,如果拿到锁,就执行if语句
                    synchronized (lock){
                        if ((count & 1)==1){
                            System.out.println(Thread.currentThread().getName()+" count:"+count);
                            count++;
                        }else {
                            System.out.println("奇数,资源浪费");
                        }
                    }
                    
                }
            }
        },"奇数").start();
    }
}

由于两个线程在竞争锁的时候是随机的,因此,会造成大量的浪费。
结果:
wait、notify、notifyAll、sleep、join、yield详解_第2张图片

1.3.2 wait实现

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();
        //中间加10ms休眠时间,确保偶数线程先执行
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new TurningRunner(),"奇数").start();
    }

    static class TurningRunner implements Runnable{
        @Override
        public void run() {
            while(count<=100){
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName()+" : "+count);
                    count++;
                    lock.notify();  //先去唤醒其他线程
                    if (count<=100){
                        try {
                            lock.wait();    //自己进入休眠,释放lock锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

执行结果:

wait、notify、notifyAll、sleep、join、yield详解_第3张图片

1.4 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 获取resourceA锁");
                    synchronized (resourceB) {
                        System.out.println("ThreadA 获取resourceB锁");
                        try {
                            System.out.println("ThreadA 释放 resourceA 锁");
                            resourceA.wait();	//线程A释放A锁
                            System.out.println("已释放");	//此处不会执行,因为一旦A锁释放,线程A就进入WAITING状态
//                            resourceB.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){	//ThreadB获取A锁
                    System.out.println("ThreadB 获取 resourceA 锁");
                    synchronized (resourceB){	//
                        System.out.println("ThreadB 获取 resourceB 锁");
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

结果:死锁
wait、notify、notifyAll、sleep、join、yield详解_第4张图片

二、sleep

sleep方法不释放锁,等sleep时间到了以后,正常结束才会释放锁,而wait方法释放锁

2.1 synchronization

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); //sleep中不会释放锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程"+Thread.currentThread().getName()+"退出了同步代码块");
    }
}

结果:
wait、notify、notifyAll、sleep、join、yield详解_第5张图片

2.2 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) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();
    }
}

结果:
wait、notify、notifyAll、sleep、join、yield详解_第6张图片

2.3 sleep中断

//每一秒打印一次时间,休眠三秒后,在休眠中进行中断
public class SleepInterrupted implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepInterrupted());
        thread.start();
        Thread.sleep(3500);
        thread.interrupt();
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(new Date());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("我被中断了!");
                e.printStackTrace();
            }
        }
    }
}

结果:
wait、notify、notifyAll、sleep、join、yield详解_第7张图片
sleep方法可以让线程进入Timed_Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。

wait和sleep的异同

  • 相同
    1. Wait和sleep方法都可以使线程阻塞,对应线程状态是Waiting或Time_Waiting。
    2. wait和sleep方法都可以响应中断Thread.interrupt()。
  • 不同
    1. wait方法的执行必须在同步方法中进行,而sleep则不需要。
    2. 在同步方法里执行sleep方法时,不会释放monitor锁,但是wait方法会释放monitor锁。
    3. sleep方法短暂休眠之后会主动退出阻塞,而没有指定时间的 wait方法则需要被其他线程中断后才能退出阻塞。
    4. wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法

三、join

3.1 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();
        System.out.println("所有子线程执行完毕");	//因此,这句话要等子线程执行完
    }
}

wait、notify、notifyAll、sleep、join、yield详解_第8张图片
不加join的执行结果:
wait、notify、notifyAll、sleep、join、yield详解_第9张图片

3.2 join中断

//子线程触发主线程中断,看主线程和子线程的执行顺序
public class JoinInterrupt {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mainThread.interrupt(); //子线程执行过程中将主线程中断
                    Thread.sleep(5000);
                    System.out.println("Thread1 执行完毕");
                } catch (InterruptedException e) {
                    System.out.println("子线程被中断");
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        System.out.println("等待子线程运行完毕");    //主线程等子线程执行

        try {
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中断"); //捕获到主线程被中断=》导致join失效,主线程直接执行最后打印语句
            thread1.interrupt();    //将中断传递给子线程,保证数据一致性
            e.printStackTrace();
        }

        System.out.println("子线程执行完毕!");

    }
}

由于join,主线程等待子线程执行,子线程触发主线程的中断,但是主线程的中断并不影响子线程,这会导致数据的不一致性,因此,在主线程中断时,要加上thread1.interrupt(),将中断传递给子线程。
结果:wait、notify、notifyAll、sleep、join、yield详解_第10张图片

3.3 join原理

主线程执行到join后,自动获取子线程的锁,等待子线程,在子线程run方法结束后,jvm自动唤醒主线程。

thread.join(); <=> synchronized (thread){ thread.wait(); }

3.4 join状态

在子线程join后,主线程进入WAITING状态,等待子线程执行完毕。

四、yield

作用:释放CPU时间片,线程状态是Runnable。yield()释放时间片,并不会释放掉锁,也不会陷入阻塞,下一次CPU调度依然可能将其调度。
定位:JVM不保证遵循(CPU资源不紧张,这时调用yield(),JVM也不会讲CPU资源释放掉)(具体的实现,不同的JVM实现不同,没办法保证代码的执行效果,为了保证程序的稳定性,一般开发中不使用yield)

你可能感兴趣的:(线程,并发,多线程)