Java多线程学习(五)——等待通知机制

2,642 阅读6分钟

等待通知机制的实现

方法wait()的作用是使当前线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程放到“预执行队列”,并在wait()所在的代码处停止执行,直到接到通知或中断为止。只能在同步方法或同步快中使用wait()方法,执行wait()后,当前线程释放锁。

方法notify()也要在同步方法或同步快中调用,在调用前也必须获得该对象的的对象级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机选出一个wait状态的线程,对其发出notify通知,使他等待获取对象锁。

在执行notify()后当前线程不会马上释放锁,会在线程退出synchronized代码块才会释放锁,呈wait状态的线程才可以获取锁。当第一个获取对象锁的wait线程运行结束释放锁后,如果该对象没有再次notify,其他wait状态的线程依然会阻塞wait状态,直到这个对象发出notify或notifyAll。

public class MyWait {

    private final Object lock;

    MyWait(Object lock){
        this.lock=lock;
    }

    public void waitTest(){
        try {
            synchronized (lock){
                System.out.println("开始 wait time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("结束 wait time = " + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class MyNotify {
    private final Object lock;

    MyNotify(Object lock){
        this.lock=lock;
    }

    public void notifyTest(){
        synchronized (lock){
            System.out.println("开始 notify time = " + System.currentTimeMillis());
            lock.notify();
            System.out.println("结束 notify time = " + System.currentTimeMillis());
        }
    }

}
public class Main {

    public static void main(String[] args){
        try {
            Object lock = new Object();
            MyWait myWait = new MyWait(lock);
            new Thread(() -> myWait.waitTest()).start();
            Thread.sleep(3000);
            MyNotify myNotify = new MyNotify(lock);
            new Thread(() -> myNotify.notifyTest()).start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
开始 wait time = 1552812964325
开始 notify time = 1552812967328
结束 notify time = 1552812967328
结束 wait time = 1552812967328

从输出内容可以看出3秒后执行notify方法,并在notify方法执行结束后才执行wait后的方法。

相关方法

  • wait() :使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
  • wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
  • notify():随机唤醒等待队列中等待同一共享资源的 “一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个线程”。
  • notifyAll():使所有正在等待队列中等待同一共享资源的 “全部线程” 退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。

线程的基本状态

转载与https://blog.csdn.net/qq_34337272/article/details/79690279

  1. 新建(new):新创建了一个线程对象。

  2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。

  3. 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。

  4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。

(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

(三). **其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  1. 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

本节代码GitHub

方法join的使用

在很多情况,主线程创建并启动子线程,如果子线程中进行大量的耗时运算,主线程往往将遭遇子线程结束之前结束。如果主线程要等待子线程执行完成之后在结束,就要使用join()方法,join()作用是等待线程对象销毁。

Thread类除了提供join()方法之外,还提供了join(long millis)、join(long millis, int nanos)两个具有超时特性的方法。这两个超时方法表示,如果线程thread在指定的超时时间没有终止,那么将会从该超时方法中返回。

public class Main {

    public static void main(String[] args) throws InterruptedException{
        Thread thread = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName()+"正在执行");
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }, "线程1");
        thread.start();
        thread.join();
        System.out.println("等待"+thread.getName()+"执行完");
    }
}
// 输出
线程1正在执行
等待线程1执行完

jain(long)与sleep(long)的区别

方法join(long)的功能是在内部使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。二sleep(long)不会释放锁。

ThreadLocal的使用

变量值共享可以使用public static变量的形式,所有线程都使用同一个public static变量,如果想实现每个线程都有自己的共享变量可以使用ThreadLocal来解决。

ThreadLocal的相关方法:

  • get():返回当前线程的此线程局部变量的副本中的值。
  • set(T value): 将当前线程的此线程局部变量的副本设置为指定的值。
  • remove():删除此线程局部变量的当前线程的值。
  • initialValue(): 返回此线程局部变量的当前线程的“初始值”。

线程变量间的隔离性

public class ThreadLocalTeat {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException{
        int count = 30;
        String name = "Thread-";
        for (int i=0; i<count; i++){
            Thread thread = new Thread(() -> {
                threadLocal.set(Thread.currentThread().getName());
                System.out.println(threadLocal.get());
            }, name+i);
            thread.start();
        }
        Thread.sleep(20000);
    }
}
// 输出
Thread-0
Thread-4
Thread-3
Thread-6
Thread-2
Thread-1
Thread-7
。。。

InheritableThreadLocal的使用

使用类InheritableThreadLocal可以在子线程中获取父线程继承下来的值。

public class InheritableThreadLocalTest extends InheritableThreadLocal {
    @Override
    protected Object childValue(Object parentValue) {
        return super.childValue(parentValue);
    }

    @Override
    protected Object initialValue() {
        return System.currentTimeMillis();
    }
}
 * @date 2019/6/18 8:28
 * @description
 */
public class InheritableTeat {
    static public class Inner{
        public static InheritableThreadLocalTest threadLocalTest = new InheritableThreadLocalTest();
    }


    public static void main(String[] args) throws InterruptedException{
        for (int i = 0; i<3; i++){
            System.out.println("在main线程中获取值:"+ Inner.threadLocalTest.get());
        }

                for (int i=0; i<3; i++){
                    new Thread(() -> {
                        System.out.println("在"+Thread.currentThread().getName()+"中获取值:"+ Inner.threadLocalTest.get());
            }, "Thread-"+i).start();
        }
        Thread.sleep(1000);

    }

}
// 输出
在main线程中获取值:1560818029616
在main线程中获取值:1560818029616
在main线程中获取值:1560818029616
在Thread-1中获取值:1560818029616
在Thread-2中获取值:1560818029616
在Thread-0中获取值:1560818029616

在使用InheritableThreadLocal类需要注意的一点是:如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的还是旧值。


欢迎关注公众号:

公众号微信