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

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

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

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

一、方法概览

【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,请注明来意。