• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

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

互联网 diligentman 2周前 (10-18) 14次浏览

多线程核心知识总结

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

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

wait,notify,notifyAll 的作用和方法

阻塞阶段

四种情况下会被唤醒

  1. 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程。
  2. 另一个线程调用这个对象的notifyAll()
  3. 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待。
  4. 线程自身调用了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的特点和性质

  1. 用必须先拥有monitor
  2. 只能唤醒其中一个
  3. 属于Object类
  4. 类似功能的Condition
  5. 同时持有多个锁的情况 :只会释放现在找到个wait()对应对象的那把锁。

wait原理

Entry Set 入口集
Wait Set 等待集
多线程核心知识总结(五)——趣解Thread和Object类中的线程相关方法

生产者消费者设计模式

/**
 * 描述:     用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方法响应中断

  1. 抛出InterruptedException
  2. 清除中断状态
/**
 * 描述:     每个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只是暂时将调度权让出

喜欢 (0)