【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

发布时间:2022-06-25 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

  • 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
  • 用3种方式实现生产者模式
  • Join和sleep和waIT期间线程的状态分别是什么?为什么?
  • @H_304_9@

    一、方法概览

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    二、wait、notify、notifyAll方法详解

    • 作用、用法:阻塞阶段唤醒阶段、遇到中断
    • 可以控制线程去休息或者唤醒

    1、阻塞阶段

    有时我们想让一个线程或者多个线程去休息一下,当我们需要它的时候,或者是时机成熟的时候,再去唤醒它,这就是上述三个方法的作用。一旦线程进入了休息的状态,就进入了阻塞阶段。在执行wait()方法的时候,需要先获得这个对象的moniter锁。

    知道以下四种情况之一发生时,才会被唤醒

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

    2、唤醒阶段

    • notify()会唤醒单个正在等待某个对象moniter的线程,唤醒的时候如果有多个线程都在等待,它只会选取其中一个,具体选择哪一个是任意的。JVM可以有自己的实现

    • notify()和wait()必须在synchronized保护的代码块或者方法中去执行,如果在synchronized外部执行,会抛出异常。

    • notifyAll()会把所有等待的线程一次性唤醒,至于哪一个线程会获得释放的moniter锁,依赖于操作系统线程的调度。

    3、遇到中断

    假设一个线程已经执行了wait()方法,在此期间,如果被中断了,会抛出InterruptedException,并且释放掉已经获取到的moniter锁。

    4、普通用法

    ①wait/notfy

    测试代码

    /**
     * 描述: 展示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的moniter锁
                        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(1000);
            thread2.start();
        }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    ②Wait/notifyAll

    /**
    * 描述: 3个线程,线程和线程2首先被阻塞,线程3唤醒它们。
    * notify,notifyAll区别
    * start先执行不代表线程先启动
    */
    public class WaitNotifyAll implements Runnable{
       private static final Object resourceA = new Object();
       @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 toend.");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
    
       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();
                       System.out.println("Thread C notified");
                   }
               }
           });
           threadA.start();
           threadB.start();
           Thread.sleep(200);
           threadC.start();
       }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    ③只释放当前moniter演示

    /**
     * 描述: 证明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();
        }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    5、wait、notify、notifyAll的特点、性质

    • 用之前必须先拥有对象的monitor
    • 只能唤醒其中一个
    • 属于Object类
    • 类似功能的Condition
    • 同时持有多个锁的情况
      • 可能会导致死锁的发生

    ①wait的原理

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    重新理解线程的六个状态,状态转化的特殊情况

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    三、手写生产者消费者设计模式

    1、为什么要使用生产者和消费者模式

    了解决生产者消费者速度不匹配的问题,而提出的设计模式

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    2、测试代码

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

    四、wait、notify常见面试问题

    1、用两个线程交替的打印0~100的奇偶数

    ①Synchronized关键字实现

    /**
     * 描述:两个线程交替打印0~100的奇偶数,用synchronized关键字实现
     */
    public class WaitNotifyPrintOddEvenSyn {
        private static int count;
        private static final Object lock = new Object();
        //新建两个线程
            //一个只处理偶数,一个只处理奇数,用位运算
            //用synchronized来通信
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (count < 100) {
                        synchronized (lock) {
                            //取出最低位,如果是1,那么是奇数,如果是0那么是偶数
                            if ((count &amp; 1) == 0) {
                                System.out.println(Thread.currentThread().getName() + ":" + count++);
                            }
                        }
                    }
                }
            },"偶数线程").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (count < 100) {
                        synchronized (lock) {
                            //取出最低位,如果是1,那么是奇数,如果是0那么是偶数
                            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();
        //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();
                            }
                        }
                    }
                }
            }
    
            public static void main(String[] args) {
                new Thread(new TurningRunner(), "偶数线程").start();
                new Thread(new TurningRunner(), "奇数线程").start();
            }
        }
    }
    

    2、手写生产者消费者设计模式

    3、为什么wait()需要在同步代码块内使用,而sleep()不需要

    为了通信的可靠,止死锁或者永久等待的发生。如果没有synchronized代码块保护,就可以在执行完wait之后,直接切换到另外一个线程,没想到对方已经执行完notify了,这样永远没有线程去唤醒,陷入永久等待,或者死锁的发生。而sleep()是针对于自己,单独线程的,不需要放到同步代码块当中。

    4、为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?

    主要是因为,wait(),notify(),notifyAll()是针对于锁级别的操作,而锁是针对于对象而言的。锁是绑定到某一个对象当中,而不是某一个线程当中。一个线程可以持有多把锁,如果把这些方法定义在Thread类里,就没有办法实现如此灵活的锁粒度逻辑了。

    5、wait方法是属于Object对象的,那调用Thread.wait会怎么样?

    这种方式是可以的,线程退出的时候会自动的执行notify(),我们在创建锁对象的时候不要使用Thread类。

    6、如何选择用notify还是notifyAll?

    目的是向唤醒一个还是多个线程?

    7、notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

    陷入到等待的状态,等待持有者再次释放,再去抢。

    8、用susPEnd()和resume()来阻塞线程可以吗?为什么?

    由于安全问题,被弃用了,不推荐。推荐使用wait()和notify()来实现

    五、sleep方法详解

    1、作用

    我只想让线程在预期的时间执行,其他时候不要占用CPU资

    特点:不释放锁,包括ynchronized和lock

    和wait不同

    2、演示不释放synchronized

    /**
     * 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
     */
    public class SleepDontReleaSEMonitor implements Runnable{
        @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() + "退出了同步代码块");
        }
    
        public static void main(String[] args) {
            SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
            new Thread(sleepDontReleaseMonitor).start();
            new Thread(sleepDontReleaseMonitor).start();
        }
    }
    
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    3、演示不释放lock锁

    /**
     * 描述:演示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();
        }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    4、sleep方法响应中断

    1、抛出InterruptedException

    2、清除中断状态

    /**
     * 描述: 每隔1秒钟输出当前时间,被中断,观察。
     * Thread.sleep()
     * TimeUnit.SECONDS.sleep()
     */
    public class SleepInterrupted implements Runnable{
        @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();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new SleepInterrupted());
            thread.start();
            Thread.sleep(6500);
            thread.interrupt();
        }
    }
    

    5、一句话总结

    sleep方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间之后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。

    6、常见面试问题

    ①wait/notify、sleep异同(方法属于那个对象?线程状态怎么切换?)

    • 相同
      • 都会让线程进入阻塞状态
      • 响应中断
    • 不同
      • wait/notify要在同步方法、同步代码块中去执行,防止死锁,或者永久等待
      • 释放锁:wait()会释放锁,sleep()不会释放
      • 指定时间:sleep必须传参
      • 所属类:原因:Object类,锁是针对于对象而言的,而不是线程。可以满足一个线程多把锁。

    六、join方法详解

    1、join方法作用、用法

    作用:因为新的线程加入了我们,所以我们要等他执行完再出发

    用法:main等待thread1执行完毕,注意谁等谁

    2、普通用法

    /**
     * 描述:演示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("所有子线程执行完毕");
        }
    }
    

    运行结果:

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    取消掉join注释:

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    3、遇到中断

    /**
     * 描述: 演示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 finished ");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread1.start();
            System.out.println("等待子线程运行完毕");
            try {
                //这里意思是主线程在等待子线程,但是被子线程打断
                thread1.join();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被中断了");
                e.printStackTrace();
            }
            System.out.println("子线程运行完毕");
        }
    }
    

    运行结果

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    通过上述结果分析,主线程打印了子线程运行完毕,但其实子线程并没有运行完毕,这是因为子线程中断了主线程之后,进入休眠,过了5秒才执行下一句。

    改进:
    /**
     * 描述: 演示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 finished ");
                    } catch (InterruptedException e) {
                        System.out.println(Thread.currentThread().getName() + "被中断了");
                    }
                }
            });
            thread1.start();
            System.out.println("等待子线程运行完毕");
            try {
                //这里意思是主线程在等待子线程,但是被子线程打断
                thread1.join();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被中断了");
                thread1.interrupt();
            }
            System.out.println("子线程运行完毕");
        }
    }
    

    运行结果:

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    相当于是把这个中断由主线程又传递给了子线程。

    4、join期间线程是什么状态:Waiting

    /**
     * 描述;  先join再mainThread.getState();
     * 通过debugger看各种线程的状态
     */
    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("子线程运行完毕");
        }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    5、join源码分析

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    我们可以看到,在join的源码中使用了wait()方法,使得主线程进入等待,那么join()结束之后,并没有执行notify()方法,是谁让主函数唤醒的呢?这其实是JVM的一个实现,在Thread类执行完run函数之后,会自动的notify。这也是为什么,不把wait()方法放到Thread类里面了。

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    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();
                //不用唤醒,因为子线程run方法执行完之后,自动唤醒
            }
            System.out.println("所有子线程执行完毕");
        }
    }
    

    【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法

    七、yield方法详解

    作业:释放我的CPU时间片

    定位:JVM不保证遵循

    和sleep区别:是否随时可能再次被调度,yield可以立刻的被调度,而sleep不可以

    脚本宝典总结

    以上是脚本宝典为你收集整理的【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法全部内容,希望文章能够帮你解决【Kill Thread Part.1-5】趣解Thread和Object类中线程相关方法所遇到的问题。

    如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

    本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
    如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。