十三:读写锁
1.读写锁初步认识
public class Demo1 {
private Map<String, Object> map=new HashMap<>();
private ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private ReadLock readLock=readWriteLock.readLock();//读锁
private WriteLock writeLock=readWriteLock.writeLock();//写锁
//读操作
public Object get(String key) {
try {
System.out.println(Thread.currentThread().getName()+"正在读操作-----");
readLock.lock();
Thread.sleep(3000);
return map.get(key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName()+"读操完毕-----");
}
return null;
}
//写操作
public void put(String key,Object obj) {
try {
System.out.println(Thread.currentThread().getName()+"正在写操作");
writeLock.lock();
Thread.sleep(3000);
map.put(key, obj);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName()+"写操作完毕");
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
new Thread(new Runnable() {
@Override
public void run() {
demo1.put("1", "1");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo1.get("1"));
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo1.put("3", "3");
}
}).start();
}
}
通过上面例子可以发现,读锁是一个共享锁,写锁是一个排它锁,写写之间互斥,读写之间互斥,读读之间不互斥,读读性能较高。
2.ReentrantReadWriteLock原理
通过new ReentrantReadWriteLock()创建ReentrantReadWriteLock对象时,在其构造方法就自动创建了ReadLock与WriteLock对象,还创建了公平同步器或非公平同步器,对于ReadLock,WriteLock,Sync都是ReentrantReadWriteLock的内部类,在创建ReadLock及WriteLock又将当前对象(ReentrantReadWriteLock)传入
所以ReadLock及WriteLock中的同步器就是外部类ReentrantReadWriteLock的同步器。
在调用ReadLock及WriteLock中的lock()或unlock()方法就是在调用同步器中对应的方法。
问题:上面提到过,读锁是共享锁,写锁是排它锁,那么对于读写锁状态(写锁重入次数,读锁个数,每个读书重入个数)的保存是如何实现的?
ReentrantReadWriteLock 也是基于AQS实现的,它的自定义同步器(继承AQS)需要在同步状态(一个int变量state)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。如果在一个int变量上维护多种状态,就一定需要“按位切割使用”这个变量(int占4个字节,32位),读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。
写锁的获取与释放
写锁的获取:
//获取写锁
public void lock() {
sync.acquire(1);
}
//AQS实现的独占式获取同步状态方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//自定义重写的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); //取同步状态state的低16位,写同步状态
if (c != 0) {//c不等于0表示当前线程不是第一次进来,表示重入
//存在读锁或当前线程不是已获取写锁的线程,返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//判断同一线程获取写锁是否超过最大次数,支持可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)//是否大于65535,当前线程获取写锁最大次数就65535次
throw new Error("Maximum lock count exceeded");
setState(c + acquires);//状态+1
return true;
}
//此时c=0,读锁和写锁都没有被获取
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))//设置状态
return false;
setExclusiveOwnerThread(current);//将写锁线程设置为当前线程
return true;//返回true写锁获就取到
}
//FairSync中需要判断是否有前驱节点,如果有则返回false,否则返回true。遵循FIFO
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
//NonfairSync中直接返回false,可插队。
final boolean writerShouldBlock() {
return false;
}
写锁的释放:
//写锁释放
public void unlock() {
sync.release(1);
}
//AQS提供独占式释放同步状态的方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//自定义重写的tryRelease方法
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())//判断是否是独占锁
throw new IllegalMonitorStateException();
int nextc = getState() - releases; //同步状态减去1
//判断同步状态的低16位(写同步状态)是否为0,如果为0则返回true,否则返回false.
//因为支持可重入
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);//设置持有写锁的线程为null
setState(nextc); //设置状态
return free;
}
读锁的获取与释放
读锁的获取:
public void lock() {
sync.acquireShared(1);
}
//使用AQS提供的共享式获取同步状态的方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//自定义重写的tryAcquireShared方法,参数是unused,因为读锁的重入计数是内部维护的
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//exclusiveCount(c)取低16位写锁。存在写锁且当前线程不是获取写锁的线程,返回-1,获取读锁失败。
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); //取高16位读锁,
//readerShouldBlock()用来判断当前线程是否应该被阻塞
if (!readerShouldBlock() &&
r < MAX_COUNT && //MAX_COUNT为获取读锁的最大数量,为16位的最大值
compareAndSetState(c, c + SHARED_UNIT)) {
//firstReader是不会放到readHolds里的, 这样,在读锁只有一个的情况下,就避免了查找readHolds。
if (r == 0) { // 是 firstReader,计数不会放入 readHolds。
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) { //当前线程重入
firstReaderHoldCount++;
} else {//并不是线程重入,而是另外一个线程需要获取读锁
HoldCounter rh = cachedHoldCounter; //读锁重入计数缓存,基于ThreadLocal实现
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;//将重入次数++
}
return 1;
}
//第一次获取读锁失败,有两种情况:
//1)没有写锁被占用时,尝试通过一次CAS去获取锁时,更新失败(说明有其他读锁在申请)
//2)当前线程占有写锁,并且有其他写锁在当前线程的下一个节点等待获取写锁,除非当前线程的下一个节点被取消,否则fullTryAcquireShared也获取不到读锁
return fullTryAcquireShared(current);
}
//readerShouldBlock方法用来判断当前线程是否应该被阻塞,NonfairSync和FairSync中有不同是实现。
//FairSync中需要判断是否有前驱节点,如果有则返回false,否则返回true。遵循FIFO
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
//当head节点不为null且head节点的下一个节点s不为null且s是独占模式(写线程)且s的线程不为null时,返回true。
//目的是不应该让写锁始终等待。作为一个启发式方法用于避免可能的写线程饥饿,这只是一种概率性的作用,因为如果有一个等待的写线程在其他尚未从队列中出队的读线程后面等待,那么新的读线程将不会被阻塞。
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
//用于记录读锁的次数及线程id
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
//通过ThreadLocal保存每个读锁的状态
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
当第一次获取读锁失败时,就调用fullTryAcquireShared(current)获取
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {//使用死循环
int c = getState();
//如果当前线程不是写锁的持有者,直接返回-1,结束尝试获取读锁,需要排队去申请读锁
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
//如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
//如果当前线程是第一个获取了写锁,那其他线程无法申请写锁
// assert firstReaderHoldCount > 0;
} else {
//从readHolds中移除当前线程的持有数,然后返回-1,然后去排队获取读锁。
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
//示成功获取读锁,后续就是更新readHolds等内部变量,
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh;
}
return 1;
}
}
}
读锁的释放:
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//更新计数
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//自旋CAS,减去1<<16
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
3.锁降级
锁降级:指将写锁降级为读锁。
在写锁没有释放的时候,获取读锁,在释放写锁
public class Demo2 {
private Map<String, Object> map=new HashMap<>();
private ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private ReadLock readLock=readWriteLock.readLock();//读锁
private WriteLock writeLock=readWriteLock.writeLock();//写锁
private boolean isUpdate;
public void readWrite() {
readLock.lock();
if (isUpdate) {
readLock.unlock();
writeLock.lock();
map.put("xxx", "xxx");
readLock.lock();//锁降级
writeLock.unlock();
}
System.out.println(map.get("xxx"));
readLock.unlock();
}
}
十四:线程安全性问题总结
1.出现线程安全性问题的条件
1)在多线程的环境下
2)有共享资源
3)对共享资源进行非原子性操作
2.解决线程安全性问题的方法
1)synchronized (偏向锁,轻量级锁,重量级锁)
2)volatile
2)JDK提供的原子类
3)使用Lock(共享锁,排它锁)
十五:线程间通信
1.等待唤醒机制(wait/notifyAll/notify)
public class Demo3 {
private volatile int signal;
public synchronized int getSignal() {
System.out.println(Thread.currentThread().getName()+"获取状态执行---------");
if (this.signal!=1) {
try {
//wait执行完毕后会释放对象锁
wait();//wait/notifyAll/notify必须在同步代码块中执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"获取状态执行完毕---------");
return signal;
}
public synchronized void setSignal() {
this.signal = 1;
notifyAll();//唤醒所有线程执行
// notify();//随机唤醒一个线程执行
System.out.println("notifyAll执行完毕后,线程睡眠开始----");
try {
Thread.sleep(3000);//线程睡眠,只有notifyAll/notify执行完毕释放锁后,等待线程才能重新获取锁,在执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo3 demo1 = new Demo3();
Targer1 targer1 = new Targer1(demo1);
Targer2 targer2 = new Targer2(demo1);
new Thread(targer2).start();
new Thread(targer2).start();
new Thread(targer2).start();
new Thread(targer2).start();
try {
TimeUnit.SECONDS.sleep(1);//线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(targer1).start();
}
}
public class Targer1 implements Runnable {
private Demo3 demo3;
public Targer1(Demo3 demo3) {
super();
this.demo3 = demo3;
}
@Override
public void run() {
demo3.setSignal();
}
}
public class Targer2 implements Runnable {
private Demo3 demo3;
public Targer2(Demo3 demo3) {
super();
this.demo3 = demo3;
}
@Override
public void run() {
demo3.getSignal();
}
}
2.Condition简单使用
实现三个线程依次顺序执行
使用wait及notifyAll实现:
public class Demo1 {
private int signal;
public synchronized void a() {
while(signal!=0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
signal++;
notifyAll();
}
public synchronized void b() {
while(signal!=1) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
signal++;
System.out.println("B");
notifyAll();
}
public synchronized void c() {
while (signal!=2) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
signal=0;
System.out.println("C");
notifyAll();
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
A a = new A(demo1);
B b = new B(demo1);
C c = new C(demo1);
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
class A implements Runnable{
private Demo1 demo1;
public A(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.a();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
private Demo1 demo1;
public B(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.b();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class C implements Runnable{
private Demo1 demo1;
public C(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.c();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用Condition实现:
public class Demo1 {
private int signal;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
public void a() {
lock.lock();
while(signal!=0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
signal++;
condition1.signal();
lock.unlock();
}
public void b() {
lock.lock();
while(signal!=1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
signal++;
System.out.println("B");
condition2.signal();
lock.unlock();
}
public void c() {
lock.lock();
while (signal!=2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
signal=0;
System.out.println("C");
condition.signal();
lock.unlock();
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
A a = new A(demo1);
B b = new B(demo1);
C c = new C(demo1);
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
class A implements Runnable{
private Demo1 demo1;
public A(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.a();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
private Demo1 demo1;
public B(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.b();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class C implements Runnable{
private Demo1 demo1;
public C(Demo1 demo1) {
super();
this.demo1 = demo1;
}
@Override
public void run() {
while (true) {
demo1.c();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.Condition原理解析
首先我们需要了解同步队列和等待队列的概念。简单的理解是同步队列存放着竞争同步资源的线程的引用(不是存放线程),而等待队列存放着待唤醒的线程的引用。
Condition实现等待的时候内部也有一个等待队列,等待队列是一个隐式的单向队列,等待队列中的每一个节点也是一个AbstractQueuedSynchronizer.Node实例。
每个Condition对象中保存了firstWaiter和lastWaiter作为队列首节点和尾节点,每个节点使用Node.nextWaiter保存下一个节点的引用,因此等待队列是一个单向队列。
每当一个线程调用Condition.await()方法,那么该线程会释放锁,构造成一个Node节点加入到等待队列的队尾。
Condition.await()方法的源码(等待)如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //构造一个新的等待队列Node加入到队尾
int savedState = fullyRelease(node); //释放当前线程的独占锁,不管重入几次,都把state释放为0
int interruptMode = 0;
//如果当前节点没有在同步队列上,即还没有被signal,则将当前线程阻塞
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//下面的if判断都是和中断相关的,主要是区分两种中断:是在被signal前中断还是在被signal后中断,如果是被signal前就被中断则抛出 InterruptedException,否则执行 Thread.currentThread().interrupt();
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) //被中断则直接退出自旋
break;
}
//退出了上面自旋说明当前节点已经在同步队列上,但是当前节点不一定在同步队列队首。acquireQueued将阻塞直到当前节点成为队首,即当前线程获得了锁。然后await()方法就可以退出了,让线程继续执行await()后的代码。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
final boolean isOnSyncQueue(Node node) {
//如果当前节点状态是CONDITION或node.prev是null,则证明当前节点在等待队列上而不是同步队列上。之所以可以用node.prev来判断,是因为一个节点如果要加入同步队列,在加入前就会设置好prev字段。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果node.next不为null,则一定在同步队列上,因为node.next是在节点加入同步队列后设置的
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node); //前面的两个判断没有返回的话,就从同步队列队尾遍历一个一个看是不是当前节点。
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
Condition.signal() 方法的源码(唤醒)如下:
public final void signal() {
if (!isHeldExclusively())//如果同步状态不是被当前线程独占,直接抛出异常。
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); //通知等待队列队首的节点。
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)//让首节点的指针移到首节点的下一个节点
lastWaiter = null;
first.nextWaiter = null;//将首节点与其下一个节点断开
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
//transferForSignal方法尝试唤醒当前节点即将当前节点加入到同步队列中,如果唤醒失败,则继续尝试唤醒当前节点的后继节点,如果加入成功,await()方法的自旋就结束
}
final boolean transferForSignal(Node node) {
//如果当前节点状态为CONDITION,则将状态改为0准备加入同步队列;如果当前状态不为CONDITION,说明该节点等待已被中断,则该方法返回false,doSignal()方法会继续尝试唤醒当前节点的后继节点
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); //将节点加入同步队列,返回的p是节点在同步队列中的先驱节点
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
总结:Condition的本质就是等待队列和同步队列的交互:
当一个持有锁的线程调用Condition.await()时,它会执行以下步骤:
- 构造一个新的等待队列节点加入到等待队列队尾
- 释放锁,也就是将它的同步队列节点从同步队列队首移除
- 自旋,直到它在等待队列上的节点移动到了同步队列(通过其他线程调用signal())或被中断
- 阻塞当前节点,直到它获取到了锁,也就是它在同步队列上的节点排队排到了队首。
当一个持有锁的线程调用Condition.signal()时,它会执行以下操作:
从等待队列的队首开始,尝试对队首节点执行唤醒操作;如果节点CANCELLED,就尝试唤醒下一个节点;如果再CANCELLED则继续迭代。对每个节点执行唤醒操作时,首先将节点加入同步队列,此时await()操作的步骤3的解锁条件就已经开启了。然后分两种情况讨论:
- 如果先驱节点的状态为CANCELLED(>0) 或设置先驱节点的状态为SIGNAL失败,那么就立即唤醒当前节点对应的线程,此时await()方法就会完成步骤3,进入步骤4.
- 如果成功把先驱节点的状态设置为了SIGNAL,那么就不立即唤醒了。等到先驱节点成为同步队列首节点并释放了同步状态后,会自动唤醒当前节点对应线程的,这时候await()的步骤3才执行完成,而且有很大概率快速完成步骤4.
4.join应用与原理解析
简单使用
public class Demo {
public void a(Thread joinThread) {
System.out.println("方法a执行-------");
joinThread.start();
try {
joinThread.join();//子线程执行join方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a方法执行完毕-------");
}
public void b() {
System.out.println("子线程开始执行-------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕-------");
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
demo.b();
}
});
new Thread(new Runnable() {//父线程
@Override
public void run() {
demo.a(thread);//调用子线程
}
}).start();;
}
}
原理解析
public final void join() throws InterruptedException {
join(0);
}
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) {//存活时间==0,无线循环
while (isAlive()) {//判断子线程是否存活
wait(0);//存活,则父线程等待,当子线程执行完毕,父线程在执行
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join就是父线程暂时让出cpu事件片,让子线程获取时间片执行,执行完毕后,父线程在开始执行。