ReentrantLock必须要手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是Lock必须手动释放,因此常常在finally中释放锁
package demo22;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/* ReentrantLock用来替代synchronized
* ReentrantLock必须要手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是Lock必须手动释放,因此常常在finally中释放锁*/
public class T {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock(); //加锁 //相当于synchronized(this)
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(" " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
void m2() {
lock.lock(); //加锁
System.out.println(" m2()... ");
lock.unlock(); //释放锁
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t::m2).start();
}
}
运行结果
0
1
2
3
4
5
6
7
8
9
m2()...
package demo23;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock可以进行尝试锁定tryLock();若无法锁定或在指定时间内无法锁定,线程可以决定是否等待
*/
public class T1 {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.print(" " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 使用tryLock进行尝试锁定,不管锁定与否,方法都将会继续执行,可以根据tryLock的返回值判定是否被锁定了
* 可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中。
*/
void m2() {
boolean locked = lock.tryLock();
System.out.print(" m2..." + locked + " ");
if (locked) lock.unlock(); //false 不指定尝试时间
}
public static void main(String[] args) {
T1 t = new T1();
new Thread(t::m1).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t::m2).start();
}
}
运行结果
0 m2...false 1 2 3 4 5 6 7 8 9
package demo23;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock可以进行尝试锁定tryLock();若无法锁定或在指定时间内无法锁定,线程可以决定是否等待
*/
public class T2 {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.print(" " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 使用tryLock进行尝试锁定,不管锁定与否,方法都将会继续执行,可以根据tryLock的返回值判定是否被锁定了
* 可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中。
*/
void m2() {
boolean locked = false;
try {
locked = lock.tryLock(5, TimeUnit.SECONDS); //指定超时时间为5s
System.out.println(" m2..." + locked + " ");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
T2 t = new T2();
new Thread(t::m1).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t::m2).start();
}
}
运行结果
0 1 2 3 4 5 m2...false
6 7 8 9
ReentrantLock可调用lockInterruptibly方法,对线程的interrupt方法作出响应,在一个线程等待的过程中,可以被打断。
package demo24;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*ReentrantLock可调用lockInterruptibly()方法,对线程的interrupt()方法作出响应,在一个线程等待的过程中,可以被打断。
* ReentrantLock的lock()方法是不能被打断的,即锁用lock()方法锁定,线程调用interrupt()方法是毫无作用的*/
public class T {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lockInterruptibly();
System.out.print(" t1 start... ");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); //t1不停的运行,睡死了
System.out.print(" t1 end... ");
} catch (InterruptedException e) {
System.out.print(" t1-interrupted! ");
} finally {
System.out.println("t1 解锁");
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
try {
lock.lock(); //不能对interrupt()方法作出响应
// lock.lockInterruptibly(); //也是上锁,但是可以对interrupt()方法作出响应
System.out.print(" t2 start... ");
TimeUnit.SECONDS.sleep(5);
System.out.print(" t2 end... ");
} catch (InterruptedException e) {
System.out.println(" t2-interrupted! ");
} finally {
try {
System.out.println("t2 解锁");
lock.unlock();
} catch (Exception e) {
System.out.println("没有得到锁的线程运行结束");
}
}
}, "t2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt(); //打断t2的等待,如果使用的是t2使用的是lock()方法进行上锁就无法打断,如果使用lockInterruptibl()方法就可以打断
}
}
运行结果
t1 start...
无法打断t2线程。
改用lockInterruptibl()
package demo24;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*ReentrantLock可调用lockInterruptibly()方法,对线程的interrupt()方法作出响应,在一个线程等待的过程中,可以被打断。
* ReentrantLock的lock()方法是不能被打断的,即锁用lock()方法锁定,线程调用interrupt()方法是毫无作用的*/
public class T {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lockInterruptibly();
System.out.print(" t1 start... ");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); //t1不停的运行,睡死了
System.out.print(" t1 end... ");
} catch (InterruptedException e) {
System.out.print(" t1-interrupted! ");
} finally {
System.out.println("t1 解锁");
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
try {
//lock.lock(); //不能对interrupt()方法作出响应
lock.lockInterruptibly(); //也是上锁,但是可以对interrupt()方法作出响应
System.out.print(" t2 start... ");
TimeUnit.SECONDS.sleep(5);
System.out.print(" t2 end... ");
} catch (InterruptedException e) {
System.out.println(" t2-interrupted! ");
} finally {
try {
System.out.println("t2 解锁");
lock.unlock();
} catch (Exception e) {
System.out.println("没有得到锁的线程运行结束");
}
}
}, "t2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt(); //打断t2的等待,如果使用的是t2使用的是lock()方法进行上锁就无法打断,如果使用lockInterruptibl()方法就可以打断
}
}
运行结果
t1 start... t2-interrupted!
t2 解锁
没有得到锁的线程运行结束
package demo25;
import java.util.concurrent.locks.ReentrantLock;
/*ReentrantLock可以指定为公平锁,构造方法中将fair属性设置为true即为公平锁,fair默认为false*/
public class T extends Thread {
private static ReentrantLock lock = new ReentrantLock(true); //参数为true表示为公平锁,可对比输出结果
@Override
public void run() {
for (int i = 0; i < 20; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "-获得锁 ");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
T t = new T();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
运行结果
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
Thread-1-获得锁
Thread-2-获得锁
写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
下面使用wait和notifyAll方法来实现
package demo26;
import java.util.LinkedList;
/**
* 写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
* wait和notifyAll方法来实现。
*/
public class MyContainer1 {
final private LinkedList<Object> list = new LinkedList<Object>();
final private int MAX = 10; //最多十个元素
private int count = 0;
public synchronized void put(Object t) {
while (list.size() == MAX) { //想想为什么用while而不是if
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
++count;
this.notifyAll(); //通知消费者进程进行消费
}
public synchronized Object get() {
Object t = null;
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = list.removeFirst();
count--;
this.notifyAll(); //通知生产者进程进行生产
return t;
}
public static void main(String[] args) {
MyContainer1 pc = new MyContainer1();
for (int i = 0; i < 10; i++) { //10个消费者
new Thread(() -> {
for (int j = 0; j < 5; j++) { //每个消费者最多消费5个
System.out.println("消费者线程" + Thread.currentThread().getName() + ",开始消费: " + pc.get());
}
}, "c" + i).start();
}
//启动生产者线程
for (int i = 0; i < 2; i++) { //2个生产者
new Thread(() -> {
for (int j = 0; j < 25; j++) { //每个生产者最多生产25个
pc.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
运行结果
消费者线程c9,开始消费: p0 0
消费者线程c9,开始消费: p0 1
消费者线程c8,开始消费: p0 2
消费者线程c7,开始消费: p0 3
消费者线程c0,开始消费: p0 4
消费者线程c6,开始消费: p0 5
消费者线程c7,开始消费: p0 6
消费者线程c5,开始消费: p0 7
消费者线程c0,开始消费: p0 8
消费者线程c6,开始消费: p0 9
消费者线程c4,开始消费: p0 11
消费者线程c9,开始消费: p0 10
消费者线程c1,开始消费: p0 12
消费者线程c8,开始消费: p0 13
消费者线程c5,开始消费: p0 14
消费者线程c6,开始消费: p0 15
消费者线程c3,开始消费: p0 16
消费者线程c2,开始消费: p0 17
消费者线程c4,开始消费: p0 18
消费者线程c6,开始消费: p0 19
消费者线程c8,开始消费: p0 20
消费者线程c7,开始消费: p0 21
消费者线程c7,开始消费: p0 24
消费者线程c4,开始消费: p0 23
消费者线程c9,开始消费: p0 22
消费者线程c9,开始消费: p1 0
消费者线程c0,开始消费: p1 4
消费者线程c0,开始消费: p1 5
消费者线程c0,开始消费: p1 6
消费者线程c1,开始消费: p1 7
消费者线程c1,开始消费: p1 8
消费者线程c1,开始消费: p1 9
消费者线程c1,开始消费: p1 10
消费者线程c5,开始消费: p1 3
消费者线程c5,开始消费: p1 11
消费者线程c5,开始消费: p1 12
消费者线程c7,开始消费: p1 13
消费者线程c8,开始消费: p1 2
消费者线程c6,开始消费: p1 16
消费者线程c8,开始消费: p1 17
消费者线程c4,开始消费: p1 1
消费者线程c3,开始消费: p1 15
消费者线程c2,开始消费: p1 14
消费者线程c3,开始消费: p1 19
消费者线程c3,开始消费: p1 21
消费者线程c4,开始消费: p1 18
消费者线程c3,开始消费: p1 22
消费者线程c2,开始消费: p1 20
消费者线程c2,开始消费: p1 23
消费者线程c2,开始消费: p1 24
这里我们可以想想两个问题:
public synchronized void put(Object t)
方法中的判断while (list.size() == MAX) { //想想为什么用while而不是if
这里为什么需要用while?而不用if?public synchronized void put(Object t)
方法中的this.notifyAll(); //通知消费者进程进行消费
为啥这里使用的是notifyAll()而不是notify()方法?答1:
我们假设判断是if的情况如下:
public synchronized void put(Object t) {
if (list.size() == MAX) {
try {
this.wait();
//1. 假如有一个线程t1满足了size==MAX的情况,然后一进来就被wait了,并且释放了锁,线程t1等待在这里。
//2. 这个时候其他的线程t2获取了锁,也调用了put的方法,这个时候判断是满的,所以t2线程也是释放了锁并wait等待在这里了。
//3. 这个时候其他线程t3获取了锁,这时候调用的是get方法了。get方法执行完之后会size-1,这个时候size不是满的了,然后notifyAll,叫醒等待的线程。
//4. 这时候刚好叫醒了等待的线程t1,t1线程重新获取锁,并从wait()方法继续往下执行,因为我们已经判断过if了,所以往下执行add的操作了,这个时候size+1了,又变成满的了,最后又调用了notifyAll方法。叫醒等待的线程
//5. 这个时候刚刚好叫醒的是t2线程了,t2线程重新获取锁了,并从wait方法继续往下执行,因为if条件判断过了,所以这个时候不会再判断size是不是满了,但是这个时候size已经是满的了,但还是继续往下执行add的操作,所以size最终就变成了MAX+1。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
++count;
this.notifyAll();
}
所以我们需要用while,让线程重新在判断一下size是不是满了。所以同理get方法中也是需要用while
答2:
这里假设使用notify的情况如下:
public synchronized void put(Object t) {
while (list.size() == MAX) { //想想为什么用while而不是if
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
++count;
this.notify(); //通知消费者进程进行消费
//在某些情况下可能会出现问题,举个例子比如我现在有50个消费者线程,2个生产者线程
//1. 运气不好一开始运行了20个消费者线程,然后发现size都为0,全部等待,等待了20个消费线程
//2. 生产者线程开始执行了,而且再没加到size为MAX之前,CPU都恰巧调度了生产者线程,调度了10次,所以会叫醒10个消费者线程,还有10个消费线程在等待,然后现在size已经满了。
//3. CPU又不巧调度了生产者线程,判断满了,所以wait了一个生产者线程,还剩下一个生产者线程,但是不巧的是,下次执行的还是一个生产者线程,判断满了,又wait了一个生产者线程,此时生产者线程都在等待了。
//4. 还好我们还有消费者线程,接下来依次调度了10个消费者线程,所以会叫醒10个线程,但是叫醒的这10个线程不巧刚刚好是之前等待的那10个消费者线程。并且这个时候size已经为0了,而且全剩下消费者线程了,所以这些真完蛋了,剩下的消费者都会判断size为0,然后进入等待,最终所有的线程都gg了。所有的线程都在等待,程序卡死了。
//总结:最好还是用notifyAll,别用notify
}
值得注意的是notify只是把等待的线程变成可运行的状态,到底开始执行哪个线程还是要看CPU的调度,所以调用了notify不一定下一个执行的就是wait的线程,是有可能是其他的线程。
比如下面这个例子就可以说明了。
package demo26;
public class T {
/**
* 运行结果:
*
* 主线程结束
* 线程t1 start
* 线程t2 start
* 线程t2 end
* 线程t3 start
* 线程t3 end
* 线程t4 start
* 线程t4 end
* 线程t1 end
*
*
*
* 1. t1线程开始wait
* 2. 运行t2线程
* 3. 运行t3线程,并且调用了notify方法
* 4. 运行的不是wait线程t1,而是运行了t4线程
* 5. 轮到t1线程执行了。
*
* 所以说明调用了notify只是把wait的线程叫醒了,变成可执行的状态,并不是下一个一定会执行wait的线程
*/
public static void main(String[] args) {
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程t1 start");
lock.wait();
System.out.println("线程t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程t2 start");
System.out.println("线程t2 end");
}
}, "t2").start();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程t3 start");
lock.notify();
System.out.println("线程t3 end");
}
}, "t3").start();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程t4 start");
System.out.println("线程t4 end");
}
}, "t4").start();
System.out.println("主线程结束");
}
}
运行结果
主线程结束
线程t1 start
线程t2 start
线程t2 end
线程t3 start
线程t3 end
线程t4 start
线程t4 end
线程t1 end
顺便附上一个线程的状态(参考:https://www.cnblogs.com/williamjie/p/9440846.html)
- 当一个线程执行了start方法后,不代表这个线程就会立即被执行,只代表这个线程处于可运行的状态,最终由OS的线程调度来决定哪个可运行状态下的线程被执行。
- 一个线程一次被选中执行是有时间限制的,这个时间段叫做CPU的时间片,当时间片用完但线程还没有结束时,这个线程又会变为可运行状态,等待OS的再次调度;在运行的线程里执行Thread.yeild()方法同样可以使当前线程变为可运行状态。
- 在一个运行中的线程等待用户输入、调用Thread.sleep()、调用了其他线程的join()方法,则当前线程变为阻塞状态。
- 阻塞状态的线程用户输入完毕、sleep时间到、join的线程结束,则当前线程由阻塞状态变为可运行状态。
- 运行中的线程调用wait方法,此线程进入等待队列。
- 运行中的线程遇到synchronized同时没有拿到对象的锁标记、等待队列的线程wait时间到、等待队列的线程被notify方法唤醒、有其他线程调用notifyAll方法,则线程变成锁池状态。
- 锁池状态的线程获得对象锁标记,则线程变成可运行状态。
- 运行中的线程run方法执行完毕或main线程结束,则线程运行结束。
package demo26;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock和Condition来实现生产者和消费者的同步容器,
* 相比使用wait/notifyAll,使用Condition的方式能更加精确地指定哪些线程被唤醒。
*/
public class MyContainer2 {
final private LinkedList<Object> list = new LinkedList<>();
final private int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(Object obj) {
try {
lock.lock();
while (list.size() == MAX) {
producer.await();
}
list.add(obj);
++count;
consumer.signalAll(); //通知消费者进行消费
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public Object get() {
Object obj = null;
try {
lock.lock();
while (count == 0) {
consumer.await();
}
obj = list.removeFirst();
count--;
producer.signalAll(); //通知生产者进行生产
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return obj;
}
public static void main(String[] args) {
MyContainer2 c = new MyContainer2();
for (int i = 0; i < 10; i++) { //10个消费者
new Thread(() -> {
for (int j = 0; j < 5; j++) { //每个消费者最多消费5个
System.out.println("消费者线程" + Thread.currentThread().getName() + ",开始消费: " + c.get());
}
}, "c" + i).start();
}
//启动生产者线程
for (int i = 0; i < 2; i++) { //2个生产者
new Thread(() -> {
for (int j = 0; j < 25; j++) { //每个生产者最多生产25个
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
运行结果
消费者线程c0,开始消费: p0 0
消费者线程c0,开始消费: p1 4
消费者线程c0,开始消费: p1 7
消费者线程c4,开始消费: p1 3
消费者线程c3,开始消费: p1 2
消费者线程c0,开始消费: p0 3
消费者线程c9,开始消费: p0 5
消费者线程c9,开始消费: p0 8
消费者线程c9,开始消费: p0 9
消费者线程c9,开始消费: p0 10
消费者线程c1,开始消费: p1 0
消费者线程c1,开始消费: p0 11
消费者线程c1,开始消费: p0 12
消费者线程c2,开始消费: p1 1
消费者线程c2,开始消费: p0 14
消费者线程c2,开始消费: p0 15
消费者线程c2,开始消费: p0 16
消费者线程c2,开始消费: p0 17
消费者线程c1,开始消费: p0 13
消费者线程c1,开始消费: p0 18
消费者线程c4,开始消费: p0 7
消费者线程c4,开始消费: p0 19
消费者线程c4,开始消费: p0 20
消费者线程c4,开始消费: p0 21
消费者线程c0,开始消费: p0 6
消费者线程c3,开始消费: p0 4
消费者线程c3,开始消费: p0 22
消费者线程c3,开始消费: p0 23
消费者线程c3,开始消费: p0 24
消费者线程c8,开始消费: p0 2
消费者线程c8,开始消费: p1 8
消费者线程c8,开始消费: p1 9
消费者线程c8,开始消费: p1 10
消费者线程c8,开始消费: p1 11
消费者线程c7,开始消费: p0 1
消费者线程c7,开始消费: p1 12
消费者线程c7,开始消费: p1 13
消费者线程c6,开始消费: p1 6
消费者线程c5,开始消费: p1 5
消费者线程c5,开始消费: p1 16
消费者线程c5,开始消费: p1 17
消费者线程c5,开始消费: p1 18
消费者线程c6,开始消费: p1 15
消费者线程c7,开始消费: p1 14
消费者线程c6,开始消费: p1 20
消费者线程c5,开始消费: p1 19
消费者线程c6,开始消费: p1 22
消费者线程c6,开始消费: p1 23
消费者线程c7,开始消费: p1 21
消费者线程c9,开始消费: p1 24
package demo27;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal线程局部变量
* ThreadLocal是使用空间换时间,synchronized是使用时间换空间比如在hibernate中session就存在与ThreadLocal中,避免synchronized的使用
* 线程局部变量属于每个线程都有自己的,线程间不共享,互不影响
* 运行下面的程序,理解ThreadLocal
*/
public class T {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set(new Person());
}).start();
}
}
class Person{
private String name = "kevin";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果
null
https://www.bilibili.com/video/av57098526
https://www.bilibili.com/video/av33688545?p=20
https://www.cnblogs.com/williamjie/p/9440846.html
https://blog.csdn.net/zl_StepByStep/article/details/88760572
https://gitee.com/cckevincyh/java_concurrent_learning