Java多线程-线程通信ThreadLocal

等待通知

wait()
wait()将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断。在调用wait()前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait(),在执行wait()后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获取锁。如果调用wait()没有持有适当的锁,则抛出异常。
notify()
notify()也要在同步方法或者同步块中调用,即调用前,线程必须获得该对象的对象级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果多个线程等待,则由线程规划器中随机挑选其中一个呈wait状态的线程,对其发布notify通知,并使它等待获取该对象的对象锁。notify()方法后,当前线程不会马上释放该对象锁,wait线程也不能马上获得对象锁,需要等待notify线程执行完毕,退出synchronized代码后,释放线程锁才可以。
当第一个获得该对象锁的wait线程运行完成后,它会释放对象锁,但是如果没有执行notify()的话,即时对象空闲,其他wait状态等待的线程由于没有得到对象的通知,也还会继续阻塞,直到这个对象发出notify或者notifyAll。

//等待5秒钟,如果5秒内有线程对锁进行唤醒,则唤醒;如果没有则自动唤醒,即最多wait5秒钟
wait(5000)

notifyAll()唤醒所有的wait()中的线程

执行wait()后,锁自动释放,执行notify()后,锁却不自动释放。
当线程为wait()状态,调用对象的interrup(),会抛出异常

锁释放:
- 执行完同步代码块后锁会释放
- 执行同步代码块过程中,遇到异常导致线程终止,锁也会被释放
- 在执行同步代码块的过程中,执行了锁的wait()方法,这个线程会释放对象锁,而此线程对象会进入等待线程池中,等待被唤醒。

线程运行流程

  1. 新创建一个线程对象,调用它的start()方法,系统会为此线程分配CPU资源,是他处于Runable(可运行状态),这是一个准备阶段,如果线程抢到了CPU资源,线程就处于Running(运行)状态。
  2. Runable和Running可以相互切换,线程进入Runable有如下五中情况(1).sleep()执行时间超过指定的休眠时间;(2).阻塞IO已经返回,阻塞方法执行完毕;(3).成功获得了试图同步的监视器;(4).等待某个通知,其他线程发出了通知;(5).处于挂起状态的线程调用了 resume恢复方法;
  3. Blocked阻塞,CPU时间片分配给其他线程,出现阻塞有如下五中情况,(1).调用sleep方法,主动放弃处理器资源;(2).调用阻塞IO,该方法返回前,线程被阻塞;(3).试图获得一个同步监视器,但是监视器正在被其他线程所持有;(4).等待某个通知;(5).调用suspend方法将该线程挂起,容易导致死锁,避免使用;
  4. run方法运行结束后进入销毁阶段,整个线程执行完毕。

    每隔锁对象都有两个队列,一个就绪队列,一个阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU调度。反之,一个线程被wait后,就进入阻塞队列,等待下一次被唤醒。

join

join()作用是使所属的线程对象X正常执行run方法的任务,而使当前线程Z进行无限期的阻塞,等待线程X销毁后再执行线程Z后面的代码。

//main线程的System.out.println("hello");需要在thread执行完毕后才执行。
MyThread thread=new MyThread();
thread.start();
thread.join();
System.out.println("hello");

join内部使用wait()方法进行等待。

//源码中,一直在循环,直到被监控线程销毁。
        while (isAlive()) {
                wait(0);
            }

当join遇到interrupt()方法,则出现异常

//最多只等带2秒钟
thread.join(2000);
       //源码中,判断时间,超过delay则break;
       while (isAlive()) {
          long delay = millis - now;
             if (delay <= 0) {
                 break;
             }
             wait(delay);
             now = System.currentTimeMillis() - base;
         }

join(long)和sleep(long)区别

join(long)内部使用wait(long)来实现,所以join(long) 方法具有释放锁的特点,其他线程就可以调用此线程中的同步方法
Thread.sleep(long)不释放锁

ThreadLocal

每个线程绑定自己的值,每个线程有自己的私有数据。

    private static ThreadLocal threadLocal=new ThreadLocal();
    threadLocal.get();
    threadLocal.set("1");

第一次使用threadLocal.get();的时候值为null,如果想要他有默认值,则需要重写ThreadLocal

public class MyThreadLocal extends ThreadLocal {

    @Override
    protected Object initialValue() {
        return 1;
    }
}

ThreadLocal 的原理:

  • 每个线程都有一个 ThreadLocalMap 类型的 threadLocals 属性。
  • ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
  • 当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123)); 的 threadlocal,value 就是值。
  • 当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。

    这里写图片描述

InheritableThreadLocal

InheritableThreadLocal可以在子线程中取得浮现出继承下来的值。

InheritableThreadLocal 原理

我们可以首先可以浏览下 InheritableThreadLocal 类中有什么东西:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

其实就是重写了3个方法。

首先,当我们调用 get 方法的时候,由于子类没有重写,所以我们调用了父类的 get 方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

这里会有一个Thread.currentThread() , getMap(t) 方法,所以就会得到这个线程 threadlocals。 但是,由于子类 InheritableThreadLocal 重写了 getMap()方法,再看上述代码,我们可以看到:
其实不是得到 threadlocals,而是得到 inheritableThreadLocals。 inheritableThreadLocals 之前一直没提及过,其实它也是 Thread 类的一个 ThreadLocalMap 类型的 属性,如下 Thread 类的部分代码:

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

那么,这里看 InheritableThreadLocal 重写的方法,感觉 inheritableThreadLocals 和 threadLocals 几乎是一模一样的作用,只是换了个名字而且,那么究竟 为什么在新的 线程中 通过 threadlocal.get() 方法还能得到值呢?

这时候要注意 childValue 方法,我们可以看下它的官方说明:

 * Computes the child's initial value for this inheritable thread-local
 * variable as a function of the parent's value at the time the child
 * thread is created.  This method is called from within the parent
 * thread before the child is started.

这个时候,你明白了,是不是在 创建线程的时候做了手脚,做了一些值的传递,或者这里利用上了 inheritableThreadLocals 之类的。

其实,是的:

关键在于 Thread thread = new MyThread();
关键在于 Thread thread = new MyThread();
关键在于 Thread thread = new MyThread();

这不是一个简简单单的 new 操作。当我们 new 一个 线程的时候:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

然后:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
    init(g, target, name, stackSize, null);
}

然后:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
     ......
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
    ......
    }

这时候有一句 ‘ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);’ ,然后

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

继续跟踪:

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

当我们创建一个新的线程的时候X,X线程就会有 ThreadLocalMap 类型的 inheritableThreadLocals ,因为它是 Thread 类的一个属性。

然后

先得到当前线程存储的这些值,例如 Entry[] parentTable = parentMap.table; 。再通过一个 for 循环,不断的把当前线程的这些值复制到我们新创建的线程X 的inheritableThreadLocals 中。就这样,就ok了。

那么这样会有一个什么结果呢?

结果就是我们创建的新线程X 的inheritableThreadLocals 变量中已经有了值了。那么我在新的线程X中调用threadlocal.get() 方法,首先会得到新线程X 的 inheritableThreadLocals,然后,再根据threadlocal.get()中的 threadlocal,就能够得到这个值。

这样就避免了 新线程中得到的 threadlocals 没有东西。之前就是因为没有东西,所以才拿不到值。

所以说 整个 InheritableThreadLocal 的实现原理就是这样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值