目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Thread和Object类中线程相关方法

方法概览

方法概览

wait、notify、notifyAll

阻塞阶段
 三个方法都属于 Object 类,任何对象都可以调用这三个方法,并且这些方法都是 native final 修饰的,本地方法也不可以被重写。
 类似功能 Condition。
 wait()只释放其所属的同步代码块使用的锁对象。也就是说只释放当前 monitor 。
&emspl;有时需要一个或多个线程暂时休息一下,等到后续需要它的时候或条件成熟时再去唤醒它。
&emspl;调用 wait 的前提条件是需要获取到 monitor (也就是线程必须已经获取到 synchronized 锁),否则会抛异常
 直到以下 4 种情况之一发生时,才会被唤醒

* 另一个线程调用这个对象的 notify() 方法且刚好被唤醒的是本线程。(随机调用一个线程的 notify()方法)
* 另一个线程调用这个对象的 notifyAll()方法;(唤醒所有线程), 然后多个被唤醒的线程进行锁的抢占。
* 过了 wait(long timeout) 规定的超时时间会自动唤醒,如果传入 0 就是永久等待。
* 线程自身调用了interrupt(),一会被唤醒,然后到 RUNNABLE 再 到 TERMINATED。

唤醒阶段

* 另一个线程调用这个对象的 notify() 方法且刚好被唤醒的是本线程。(随机调用一个线程的 notify()方法)
* 另一个线程调用这个对象的 notifyAll()方法;(唤醒所有线程), 然后多个被唤醒的线程进行锁的抢占。
* 过了 wait(long timeout) 规定的超时时间会自动唤醒,如果传入 0 就是永久等待。
* 线程

中断阶段
 线程已经执行了 wait()方法,在此期间如果被 interrupt(),会抛出 InterruptedException 并且释放目前已经获得的 monitor。

示例:wait 和 notify 的基本用法

/**
 * 描述:展示 wait 和 notify 的基本用法
 * 1、研究代码的执行顺序
 * Thread-0 start...
 * Thread-1 调用了 notify()
 * Thread-1 wait(10)
 * Thread-0 get lock
 * Thread-0 调用了 notify()
 * Thread-0 wait(100)
 * Thread-1 get lock
 * Thread-1 finished
 * Thread-0 finished
 *
 * 2、证明wait释放锁
 * Thread-1 调用了 notify()
 * Thread-0 get lock
 */
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() + " start...");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " get lock");
                System.out.println(Thread.currentThread().getName() + " 调用了 notify()");
                try {
                    System.out.println(Thread.currentThread().getName() + " wait(100)");
                    object.wait(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " finished");
            }
        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println(Thread.currentThread().getName() + " 调用了 notify()");
                try {
                    System.out.println(Thread.currentThread().getName() + " wait(10)");
                    object.wait(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " get lock");
                System.out.println(Thread.currentThread().getName() + " finished");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        t1.start();
        Thread.sleep(200);
        t2.start();
    }
}

示例:3 个线程,线程 1 和线程 2 首先被阻塞,线程 3 唤醒这两个线程 notify、notifyAll


/**
 * 描述:  3个线程,线程1和线程2首先被阻塞,线程3唤醒这两个线程  notify、notifyAll
 * 两种唤醒方法的区别:
 * Thread-0 got resourceA Lock!
 * Thread-0 release resouceA Lock!
 * Thread-1 got resourceA Lock!
 * Thread-1 release resouceA Lock!
 * Thread-2 调用 notifyAll
 * Thread-1 waiting to end
 * Thread-0 waiting to end
 *
 */
public class WaitNotifyWall implements Runnable {
    private static 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() + " release resouceA Lock!");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName() + " waiting to end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyWall r = new WaitNotifyWall();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    resourceA.notifyAll();
//                    resourceA.notify();
                    System.out.println(Thread.currentThread().getName() + " 调用 notifyAll");
                }
            }
        });

        t1.start();
        t2.start();
        Thread.sleep(200);
        t3.start();
    }
}

示例:只释放其所属的同步代码块使用的锁对象。也就是说只释放当前 monitor 。

package com.xdclass.couponapp.test.threadobjectclasscommonmethods;

/**
 * 描述: 证明wait只释放当前的那把锁
 *
 * Thread-0 got resourceA lock
 * Thread-0 got resourceB lock
 * Thread-0 releases resouceA lock
 * Thread-1 got resourceA lock
 * Thread-1 tries to resouceB lock ...
 */
public class WaitNotifyReleaseOwnMonitor {
    private static volatile Object resouceA = new Object();
    private static volatile Object resouceB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            /**
             * 拿到了resouceA、resouceB,释放了resouceA
             * 持有resouceB
             */
            @Override
            public void run() {
                synchronized (resouceA) {
                    System.out.println(Thread.currentThread().getName() + " got resourceA lock");
                    synchronized (resouceB) {
                        System.out.println(Thread.currentThread().getName() + " got resourceB lock");
                        try {
                            System.out.println(Thread.currentThread().getName() + " releases resouceA lock");
                            resouceA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        /**
         * 拿到了resouceA
         * 无法获取到resouceB
         */
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resouceA) {
                    System.out.println(Thread.currentThread().getName() + " got resourceA lock");
                    System.out.println(Thread.currentThread().getName() + " tries to resouceB lock ...");
                    synchronized (resouceB) { 
			System.out.println(Thread.currentThread().getName() + " releases resouceB lock");
			resouceA.wait();
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

手写生产者消费者设计模式(wait、notify、notifyAll)

生产者消费者
为什么要使用生产者、消费者模式?
 通过设计模式解耦、从而平衡生产者和消费者的生产和消费能力,最终让两者更加顺畅的协作。

import java.util.Date;
import java.util.LinkedList;

/**
 * 描述: 用wait/notify来实现(不允许使用BlockingQueue)
 * 生产者和消费公用 EventStorage
 */
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();
    }
}

示例:两个线程交替打印 0~100 的奇偶数,用 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();
                        }
                    }
                }
            }
        }
    }
}

sleep()

sleep()方法可以让线程进入 Waiting 状态,并且不占用 CPU 资源,但是不释放锁,直到规定时间再执行,休眠期间如果被中断,会抛出异常并清除中断状态。
 sleep()不释放锁:synchronized(monitor 锁)、lock 。 wait()会释放锁。
 sleep()响应中断的方式:抛出 InterruptedException、清除中断状态。

示例:synchronized(monitor 锁)、lock 。 wait()会释放锁。

package com.xdclass.couponapp.test.threadobjectclasscommonmethods;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示:不释放synchronized
 * 
 * 线程Thread-0获取到了monitor.
 * 线程Thread-0推出了同步代码块.
 * 线程Thread-1获取到了monitor.
 * 线程Thread-1推出了同步代码块.
 */
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 instance = new SleepDontReleaseMonitor();
        new Thread(instance).start();
        new Thread(instance).start();

    }
}

/**
 *
 * 演示:不释放 lock
 *
 * 线程Thread-0获取到了lock.
 * 线程Thread-0已经苏醒
 * 线程Thread-1获取到了lock.
 * 线程Thread-1已经苏醒
 */
class SleepDontReleaseLock implements Runnable {
    private static final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "获取到了lock.");
            Thread.sleep(5000);
            System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock instance = new SleepDontReleaseLock();
        new Thread(instance).start();
        new Thread(instance).start();
    }
}

示例:每一秒钟输出当前时间、被中断、观察。

package com.xdclass.couponapp.test.threadobjectclasscommonmethods;

import java.sql.Time;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 描述:  每秒钟输出当前时间,被中断,观察。
 * Thread.sleep()
 * TimeUnit.SECONDS.sleep()
 * 
 * Tue Mar 03 13:47:21 CST 2020
 * Tue Mar 03 13:47:24 CST 2020
 * Tue Mar 03 13:47:27 CST 2020
 * 我被中断了!
 */
public class SleepInterrupted implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
                //3小时30分30秒 避免了繁琐的转换,并且如果小于0,不会抛出异常,不会中断程序。
                TimeUnit.HOURS.sleep(3);
                TimeUnit.SECONDS.sleep(30);
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                System.out.println("我被中断了!");
                e.printStackTrace();
                break;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new SleepInterrupted());
        t1.start();
        Thread.sleep(6500);
        t1.interrupt();
    }
}

wait/notify、sleep 异同?
 相同点

* 都会让线程进入阻塞状态,一个是 WAITING,另一个是 TIMED_WAITING
* 阻塞期间可以响应 interrupt,都是抛出异常,可以响应中断。

 不同点

wait/notify 必须在同步方法中执行,而sleep则不需要。
wait/notify 会释放锁,而sleep不会释放锁。
sleep()必须执行时间
sleep()属于Thread类、wait/notify 属于Object类

join()

 因为新的线程加入了我们,所以我们要等待它执行完再出发。
 main 线程等待一个 Thread 执行完。通常用于等待某些资源初始化完毕。

示例:普通用法

/**
 * 演示:  join、注意语句输出顺序,会变化。
 *
 * waiting for t1、t2
 * Thread-0执行完毕
 * Thread-1执行完毕
 * finished
 */
public class Join {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "执行完毕");
            }
        };

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();

        System.out.println("waiting for t1、t2");

        t1.join();
        t2.join();

        System.out.println("finished");
    }
}

示例:join()遇到中断

package com.xdclass.couponapp.test.threadobjectclasscommonmethods;

/**
 * 描述:  join期间被中断的效果
 * 如果主线被中断,需要将中断传递给子线程,否则子线程不会停止运行。
 * 
 * 等待t1运行完毕
 * main主线程被中断
 * Thread-0 中断!!!!!
 * 子线程运行完毕!!!!!
 */
public class JoinInterrupt {
    public static void main(String[] args) throws InterruptedException {
        //拿到主线程的引用
        Thread mainThread = Thread.currentThread();

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    //子线程对主线程进行中断
                    mainThread.interrupt();
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread().getName() + " 执行完毕!!!!!");
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " 中断!!!!!");
                }
            }
        };
        
        //创建t1线程
        Thread t1 = new Thread(r);
        t1.start();
        System.out.println("等待t1运行完毕");
        try {
            t1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "主线程被中断");
            // 主线程接收到中断,并且把中断传递给子线程
            // 这样子线程才会在sleep期间获取到中断,并且抛出异常
            t1.interrupt();
            t1.join();
        } finally {
            System.out.println("子线程运行完毕!!!!!");
        }
    }
}

示例:在 join 期间,线程到底是什么状态? :WAITING

/**
 * 描述:     先join再mainThread.getState()
 * 通过debugger看线程join前后状态的对比
 *
 * 等待子线程运行完毕
 * WAITING
 * Thread-0运行结束
 * 子线程运行完毕
 */
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());//WAITING
                    System.out.println("Thread-0运行结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        System.out.println("等待子线程运行完毕");
        thread.join();
        System.out.println("子线程运行完毕");
    }
}

join 注意点
 和 join 类似的功能 JDK 已经提供了更为成熟的类:CountDownLatch 、CyclicBarrier 类。这些类的底层都使用了 join。
 Thread 类在执行 run()方法之后会自动调用 notify()。

示例: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();
        //因为Thread类对象在执行run()方法之后会自动调用notify()。
        synchronized (thread) {
            thread.wait();//等待thread线程run()执行完之后自动调用notify()才会唤醒
        }
        System.out.println("所有子线程执行完毕");
    }
}

yield()

 释放我的 CPU 时间片,线程状态依然是 RUNNABLE 状态,并不会释放自己的锁,也不会阻塞,下次 CPU 调度依然有可能随时调度起来。
 JVM 不保证遵循 yield 原则,所以 yield 并不一定能生效。
 一些类中会用到 yield:AQS、ConcurrentHashMap、StampedLock 等……
 yield 和 sleep 区别:是否随时可能再次被调度。yield 可能被 CPU 随时再次调度。

总结

为什么 wait() 需要在同步代码块内使用,而 sleep() 不需要。
 如果没有同步代码块保护,很可能会在 wait()之前先执行 notify(),这样 wait()就永远也不会被唤醒了。会造成永久等待、死锁的情况。
 sleep()是针对当前线程的,和其他线程没什么关系,所以不需要放在同步代码块中。

为什么线程通信的方法 wait(),notify()、notifyAll()被定义在 Object 类里面?而 sleep()定义在 Thread 类里面?
 因为任何对象都可以当做同步代码块的锁,所需 wait(),notify()、notifyAll()被定义在 Object 类里面。
 因为只有线程才有睡眠的行为,其他普通的的对象没有睡眠的行为,所以 sleep()需要被定义在 Thread 类中。

wait()属于 Object 对象的,那调用 Thrad.wait()会怎么样?
 Thread 类非常特殊,线程退出的时候会自动执行对象的 notify(),这样会干扰设计好的执行流程。

如何选择用 notify()还是 notify All?
 随机唤醒一个线程,唤醒所有线程。
 抢夺锁失败的线程继续进入 WAITING 状态。

用 suspend()和 resuem()来阻塞线程可以吗?为什么?
 不可以,线程不安全,已经被 wait()、notify()替代了。

JavaSE、JavaEE、JavaME 是什么?
 标注版、企业版、移动版。

JRE、JDK、JVM 是什么关系?
 Java Development Kit 包含 JRE、JVM
 Java Runtime Environment、包含 JVM
 Java Virtual Machine

Java 版本升级都包括了那些东西的升级?
 包括了类的升级,还包括了 JVM 的升级。

Java SE 8 和 Java 1.8 和 JDK 8 是什么关系,是同一个东西吗?
 Java 1.8 == Java 8 == JavaSE 8 == JDK8


作者:Soulboy