多线程
4 线程间通信
4.1 等待/通知机制
4.1.1 什么是线程等待/通知机制
在单线程程序中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在 if 语句块中。
在多线程编程中,可能 A 线程的条件没有满足只是暂时的, 稍后其他的线程 B 可能会更新条件,使得 A 线程的条件得到满足. 所以可以将 A 线程暂停,直到满足其条件后再将线程唤醒.其伪代码:
atomics{// 原子操作
while(条件不成立){
等待
}
满足被唤醒条件后,执行后续操作
}
4.1.2 等待/通知机制的实现
wait()方法是Object类中的,可以使执行当前代码的线程等待,暂停执行,直到被notify或被中断为止。
注意:
1)wait()方法只能在同步代码块中,且由锁对象调用;
2)调用wait()方法,当前线程会立即释放锁。
其伪代码如下:
// 在调用wait()方法之前,要先获得对象的内部锁
synchronized(锁对象){
while(条件不成立){
// 通过锁对象调用wait()方法,使线程暂停,立即释放锁对象
锁对象.wait();
}
// 线程的条件满足后,继续向下执行
}
notify()是Object 类中的,可以唤醒线程,该方法也必须在同步代码块中由锁对象调用 .
如果不使用锁对象调用 wait()/notify() 会抛出IllegalMonitorStateExeption 异常.
如果有多个等待的线程,notify()方法只能唤醒其中的一个.
在同步代码块中调用 notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,所以一般将notify()方法放在同步代码块的最后. 其伪代码如下:
synchronized(锁对象){
// 执行修改保护条件的代码
// 唤醒其他线程
锁对象.notify();
}
实例代码:
package com.yupeng.wait;
import java.sql.SQLOutput;
/**
* 通过notify唤醒等待的线程
* @author Yupeng
* @create 2020-12-09 11:35
*/
public class Test01 {
public static void main(String[] args) {
// 定义一个字符串为锁对象
String lock = "abc";
// 创建一个线程,进入等待状态
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
try {
System.out.println("线程1开始等待:" + System.currentTimeMillis());
lock.wait(); // 线程等待,会立即释放锁对象,当前线程进入等待状态
System.out.println("线程1结束等待:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 创建一个线程,通知其他线程结束等待
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("线程2开始唤醒:" + System.currentTimeMillis());
lock.notify();
System.out.println("线程2结束唤醒:" + System.currentTimeMillis());
}
}
});
// 开启线程1
t1.start();
// main线程睡眠2秒,保证t1线程进入等待状态
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程1开启2秒后,再开启线程2来唤醒线程1
t2.start();
}
}
// 运行结果:
线程1开始等待:1607485402715
线程2开始唤醒:1607485404716
线程2结束唤醒:1607485404716
线程1结束等待:1607485404716
4.1.3 notify()方法不会立即释放锁对象
在同步代码块中调用 notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象。
package com.yupeng.wait;
/**
* notify()方法不会立即释放锁对象
* @author Yupeng
* @create 2020-12-09 11:46
*/
public class Test02 {
public static void main(String[] args) {
// 创建一个字符串作为锁对象
String lock = "abc";
// 创建一个线程,进入等待状态
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
try {
System.out.println("线程1开始等待:" + System.currentTimeMillis());
lock.wait();
System.out.println("线程1结束等待:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 创建另一个线程,唤醒其他线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println("线程2开始唤醒:" + System.currentTimeMillis());
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
if (i == 5){
lock.notify();
System.out.println("线程2结束唤醒:" + System.currentTimeMillis());
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
// 开启线程1
t1.start();
// main线程睡眠2秒,保证t1线程进入等待状态
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程1开启2秒后,再开启线程2来唤醒线程1
t2.start();
}
}
4.1.4 interrupt()方法会中断 wait()
当线程处于wait()等待状态时, 调用线程对象的interrupt()方法会中断线程的等待状态, 产生 InterruptedException 异常。
package com.yupeng.wait;
/**
* interrupt()方法会中断 wait()
* @author Yupeng
* @create 2020-12-09 11:56
*/
public class Test03 {
public static void main(String[] args) throws InterruptedException {
//创建一个子线程对象
SubThread t1 = new SubThread();
// 开启线程
t1.start();
// main线程休眠2秒,保证t1线程进入等待状态
Thread.sleep(2000);
// 使用interrupt()方法中断wait()
t1.interrupt();
}
// 定义一个常量作为锁对象
public static final Object LOCK = new Object();
static class SubThread extends Thread{
@Override
public void run() {
synchronized (LOCK) {
try {
System.out.println("begin wait...");
LOCK.wait();
System.out.println("end wait...");
} catch (InterruptedException e) {
System.out.println("等待被中断...");
e.printStackTrace();
}
}
}
}
}
// 运行结果:
begin wait...
等待被中断...
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.yupeng.wait.Test03$SubThread.run(Test03.java:27)
4.1.5 notify()与 notifyAll()
notify()一次只能唤醒一个线程,如果有多个等待的线程,只能随机唤醒其中的某一个; 想要唤醒所有等待线程,需要调用notifyAll().
package com.yupeng.wait;
/**
* notifyAll()唤醒所有线程
* @author Yupeng
* @create 2020-12-09 15:03
*/
public class Test04 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
SubThread t1 = new SubThread(lock);
SubThread t2 = new SubThread(lock);
SubThread t3 = new SubThread(lock);
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
// main线程睡眠1000毫秒,保证3个线程都进入准备状态
Thread.sleep(1000);
synchronized (lock){
// lock.notify(); // 只有一个线程被唤醒,其他线程仍处于等待状态,对于处于等待状态的线程来说,错过了通知信号,这种现象也称为信号丢失
lock.notifyAll(); // 3个线程都被唤醒
}
}
static class SubThread extends Thread{
private Object lock;
public SubThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName() + "-->begin wait...");
lock.wait();
System.out.println(Thread.currentThread().getName() + "-->end wait...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 运行结果:
t1-->begin wait...
t3-->begin wait...
t2-->begin wait...
t2-->end wait...
t3-->end wait...
t1-->end wait...
4.1.6 wait(long)的使用
wait(long)是带有 long 类型参数的 wait()等待,如果在参数指定的时间内没有被唤醒,超时后会自动唤醒。
package com.yupeng.wait;
/**
* wait(long)的使用
* @author Yupeng
* @create 2020-12-09 15:14
*/
public class Test05 {
public static void main(String[] args) {
final Object obj = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("线程开始等待...");
obj.wait(3000); // 如果3000毫秒内没有被唤醒,会自动唤醒
System.out.println("线程结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
4.1.6 notify()通知过早
线程 wait()等待后,可以调用 notify()唤醒线程, 但如果 notify()唤醒的过早,在等待之前就调用了 notify()可能会打乱程序正常的运行逻辑.
package com.yupeng.wait;
/**
* notify()通知过早
* @author Yupeng
* @create 2020-12-09 15:17
*/
public class Test06 {
public static void main(String[] args) {
final Object obj = new Object();
// 创建等待线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("begin wait...");
obj.wait();
System.out.println("end wait!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 创建唤醒线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
System.out.println("begin notify...");
obj.notify();
System.out.println("end notify!");
}
}
});
// 如果先开启t1,再开启t2,大多数情况下,t1先等待,t2再把t1唤醒
// t1.start();
// t2.start();
// 如果先开启t2通知线程,再开启t1等待线程,可能会出现t1线程等待没有收到通知的情况
t2.start();
t1.start();
}
}
可以通过增加判断标志,来解决以上问题:
package com.yupeng.wait;
/**
* 解决notify()通知过早
* @author Yupeng
* @create 2020-12-09 15:17
*/
public class Test07 {
static boolean isFirst = true; // 定义静态变量,作为是否是第一个运行的标志
public static void main(String[] args) {
final Object obj = new Object();
// 创建等待线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
while (isFirst){ // 如果当线程是第一个开启的线程,就等待
try {
System.out.println("begin wait...");
obj.wait();
System.out.println("end wait!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
// 创建唤醒线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
System.out.println("begin notify...");
obj.notify();
System.out.println("end notify!");
isFirst = false; // 修改标志
}
}
});
// 实际上,调用start()仅仅是告诉线程调度器,当前线程准备就绪,而线程调度器在什么时候开启这个线程是不确定的,即调用start()方法的顺序,不一定是线程实际开启的顺序.
// 在当前示例中,t1等待后让t2线程唤醒, 如果t2线程先唤醒了,就不让t1线程等待了
t2.start();
t1.start();
}
}
4.1.7 wait 等待条件发生了变化
在使用 wait/nofity 模式时,如果wait 条件发生了变化,也可能会造成逻辑的混乱。
示例代码:1)定义一个集合;2)定义一个子线程,向集合中添加数据,添加完数据后,通知另外的线程从集合中取数据;3)定义一个子线程从集合中取数据,如果集合中没有数据就等待。
package com.yupeng.wait;
import java.util.ArrayList;
import java.util.List;
/**
* 如果wait条件发生了变化,也可能会造成逻辑的混乱。
* @author Yupeng
* @create 2020-12-09 15:34
*/
public class Test08 {
public static void main(String[] args) throws InterruptedException {
// 定义添加数据的线程对象
ThreadAdd threadAdd = new ThreadAdd();
// 定义取出数据的线程对象
ThreadSubtract threadSubtract = new ThreadSubtract();
threadSubtract.setName("subtract-1");
// 测试一:先开启添加数据的线程,再开启一个取数据的线程,大多数情况下会正常取出数据
// threadAdd.start();
// threadSubtract.start();
// 测试二:先开启取数据的线程,再开启添加数据的线程,取数据的线程会先等待,等到添加数据之后,再取数据
// threadSubtract.start();
// threadAdd.start();
// 测试三:开启两个取数据的线程,再开启添加数据的线程
ThreadSubtract threadSubtract2 = new ThreadSubtract();
threadSubtract2.setName("subtract-2");
threadSubtract.start();
threadSubtract2.start();
Thread.sleep(100);
threadAdd.start();
/*
程序可能出现以下异常:
subtract-1 begin wait...
subtract-2 begin wait...
Thread-0向集合中添加了一个数据
subtract-2 end wait!
subtract-2从集合中取出了data后,集合中的数据数量为0
subtract-1 end wait!
Exception in thread "subtract-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0*/
/*分析可能的执行顺序
* 两个threadSubtract子线程先启动,取数据时,集合中没有数据,wait()等待
* threadAdd线程获得CPU执行权,添加数据,然后将两个取数据子线程唤醒
* subtract-2结束wait,正常取出数据
* subtract-1结束wait,然后再执行list.remove(0)取数据时,现在list集合中已经没有数据了,这时会产生java.lang.IndexOutOfBoundsException异常
* 所以出现异常的原因是: 向 list 集合中添加了一个数据,remove()了两次
* */
/*解决方法是当等待的线程被唤醒后,再判断一次集合中是否有数据可取.即需要把subtract()方法中的if判断改为while*/
}
// 1)定义List集合
static List<String> list = new ArrayList<>();
// 2)定义方法,从集合中取出数据
public static void subtract() {
synchronized (list){
//if (list.size() == 0){
while (list.size() == 0){
try {
System.out.println(Thread.currentThread().getName() + " begin wait...");
list.wait();
System.out.println(Thread.currentThread().getName() + " end wait!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = list.remove(0);
System.out.println(Thread.currentThread().getName() + "从集合中取出了" + data + "后,集合中的数据数量为" + list.size());
}
}
// 3)定义方法,向集合中添加数据
public static void add() {
synchronized (list) {
list.add("data");
System.out.println(Thread.currentThread().getName() + "向集合中添加了一个数据");
list.notifyAll();
}
}
// 4)定义线程类,调用subtract()方法
static class ThreadSubtract extends Thread{
@Override
public void run() {
subtract();
}
}
// 5)定义线程类,调用add()方法
static class ThreadAdd extends Thread{
@Override
public void run() {
add();
}
}
}
4.2 生产者-消费值模式
在Java中,负责产生数据的模块是生产者,负责使用数据的模块是消费者. 生产者消费者解决的是数据的平衡问题,即先有数据,消费值才能使用,没有数据时,消费者需要等待。
4.2.1 生成-消费:操作值
示例代码:
定义操作数据的类
package com.yupeng.producerandconsumer.data;
/**
* 操作数据的类
* @author Yupeng
* @create 2020-12-09 18:03
*/
public class ValueOP {
private String value = "";
/**
* 定义方法,修改value的值
*/
public void setValue() {
synchronized (this) {
// 如果value值不是空串,就等待
// while (!value.equalsIgnoreCase("")) {
if (!value.equalsIgnoreCase("")) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value值是空串,就设置值
String value = System.currentTimeMillis() + "--" + System.nanoTime();
this.value = value;
System.out.println("set设置的值是:" + this.value);
this.notify();
}
}
public void getValue() {
synchronized (this) {
// 如果是value的值是空串,就等待
// while (value.equalsIgnoreCase("")) {
if (value.equalsIgnoreCase("")) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果value的值不是空串,就打印值
System.out.println("get获得的值是:" + this.value);
this.value = "";
this.notify();
}
}
}
定义线程类,模拟生产者:
package com.yupeng.producerandconsumer.data;
/**
* 定义线程类,模拟生产者
* @author Yupeng
* @create 2020-12-09 18:12
*/
public class ProducerThread extends Thread{
// 生产者生产数据,就是调用ValueOP类的setValue方法给value字段赋值
private ValueOP obj;
public ProducerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true) {
obj.setValue();
}
}
}
定义线程类,模拟消费值:
package com.yupeng.producerandconsumer.data;
/**
* 定义线程类,模拟消费值
* @author Yupeng
* @create 2020-12-09 18:12
*/
public class ConsumerThread extends Thread{
// 消费者消费数据,就是调用ValueOP类的getValue方法,获取value字段的值
private ValueOP obj;
public ConsumerThread(ValueOP obj) {
this.obj = obj;
}
@Override
public void run() {
while (true) {
obj.getValue();
}
}
}
测试单生产,单消费的情况:
package com.yupeng.producerandconsumer.data;
/**
* 测试单生产,单消费
* @author Yupeng
* @create 2020-12-09 18:15
*/
public class Test {
public static void main(String[] args) {
ValueOP valueOP = new ValueOP();
ProducerThread p1 = new ProducerThread(valueOP);
ConsumerThread c1 = new ConsumerThread(valueOP);
p1.start();
c1.start();
}
}
测试多生产,多消费的情况:
应注意两点:
(1)可能会出现以下情况:
...
set设置的值是:1607509591131--2193907297551100
get获得的值是:1607509591131--2193907297551100
get获得的值是:
set设置的值是:1607509591131--2193907297589100
get获得的值是:1607509591131--2193907297589100
get获得的值是:
...
没有获取到值,是因为生产者线程set某个值后,可能消费者线程1将该值取出,然后消费者线程2获得CPU执行权,继续执行wait()的后续代码,此时没有值,则get不到值。解决方法是将if判断条件改为while条件,如果字符串为空串,则再回到wait状态,释放锁对象。
while (!value.equalsIgnoreCase("")) {
//if (!value.equalsIgnoreCase("")) {
try {
this.wait();
...
(2)可能会出现以下情况:
...
set设置的值是:1607515525623--2199841789599300
get获得的值是:1607515525623--2199841789599300
// 空白,程序卡死
出现假死情况,可能是以下执行顺序:1)生产者线程1set某个值,字符串不是空串,然后唤醒某一个线程,释放对象锁;2)如果生产者线程2得到CPU执行权,字符串不是空串,则进入等待状态,释放对象锁,生产者线程3同理;如果生产者线程1继续得到CPU执行权,因为字符串不是空串,同样进入等待状态;3)然后消费者线程2获得CPU执行权,将该值取出并将字符串设置为空串,并唤醒某一个线程,释放对象锁;4)如果消费者线程1获得CPU执行权,发现字符串为空串,则进入等待状态,释放对象锁;同理消费者线程2和3如果获取CPU执行权,也将进入等待状态。这样,所有的生产者线程和消费者线程都进入等待状态,程序假死。
解决方法是将notify()改为notifyAll(),唤醒所有的线程,总有一个可以继续执行的线程。
4.2.2 生产-消费:操作栈
生产者把数据存储到 List 集合中, 消费者从 List 集合中取数据,使用 List 集合模拟栈。
模拟栈:
package com.yupeng.producerandconsumer.stack;
import java.util.ArrayList;
import java.util.List;
/**
* 模拟栈
* @author Yupeng
* @create 2020-12-09 20:18
*/
public class MyStack {
// 定义集合,模拟栈
private List list = new ArrayList();
// 定义常量,模拟栈的最大容量
public static final int MAX = 3;
/**
* 定义方法,模拟入栈
*/
public synchronized void push() {
// 栈中数据已满,就等待
while (list.size() >= MAX) {
System.out.println(Thread.currentThread().getName() + "begin wait...");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String data = "data-->" + Math.random();
System.out.println(Thread.currentThread().getName() + "添加了数据:" + data);
list.add(data);
this.notifyAll();
}
public synchronized void pop() {
// 栈中没有元素了,就等待
while (list.size() == 0) {
System.out.println(Thread.currentThread().getName() + "begin wait...");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "取出了数据:" + list.remove(list.size() - 1));
this.notifyAll();
}
}
生产者线程:
package com.yupeng.producerandconsumer.stack;
/**
* 生产者线程
* @author Yupeng
* @create 2020-12-09 20:31
*/
public class ProducerThread extends Thread{
private MyStack myStack;
public ProducerThread(MyStack myStack) {
this.myStack = myStack;
}
@Override
public void run() {
while (true) {
myStack.push();
}
}
}
消费者线程:
package com.yupeng.producerandconsumer.stack;
/**
* 消费者线程
* @author Yupeng
* @create 2020-12-09 20:31
*/
public class ConsumerThread extends Thread{
private MyStack myStack;
public ConsumerThread(MyStack myStack) {
this.myStack = myStack;
}
@Override
public void run() {
while (true) {
myStack.pop();
}
}
}
测试:同样应注意4.2.1节中可能出现的两个问题。
package com.yupeng.producerandconsumer.stack;
/**
* @author Yupeng
* @create 2020-12-09 20:33
*/
public class Test {
public static void main(String[] args) {
MyStack myStack = new MyStack();
ProducerThread p1 = new ProducerThread(myStack);
ProducerThread p3 = new ProducerThread(myStack);
ProducerThread p2 = new ProducerThread(myStack);
ConsumerThread c1 = new ConsumerThread(myStack);
ConsumerThread c2 = new ConsumerThread(myStack);
ConsumerThread c3 = new ConsumerThread(myStack);
p1.setName("生产者线程1");
p2.setName("生产者线程2");
p3.setName("生产者线程3");
c1.setName("消费者线程1");
c2.setName("消费者线程2");
c3.setName("消费者线程3");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
4.3 通过管道实现线程间通信
java.io包中的PipeStream管道流,用于再线程之间传送数据。一个线程发送数据到输出管道,两一个线程从输入管道中读取数据。相关的类包括:字节流PipedInputStream和PipedOutputStream,字符流PipedReader和PipedWriter。
package com.yupeng.pipeStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
/**
* 使用PipedInputStream和PipedOutputStream管道字节流再线程之间传递数据
* @author Yupeng
* @create 2020-12-09 20:44
*/
public class Test {
public static void main(String[] args) throws IOException {
// 定义管道字节流
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
// 输入管道流和输出管道流之间建立连接
inputStream.connect(outputStream);
// 创建线程,向管道流中写入数据
new Thread(new Runnable() {
@Override
public void run() {
writeData(outputStream);
}
}).start();
// 创建线程,向管道流中读取数据
new Thread(new Runnable() {
@Override
public void run() {
readData(inputStream);
}
}).start();
}
// 定义方法从管道流中写入数据
public static void writeData(PipedOutputStream out) {
// 将0-100之间的数字写入到管道流
try {
for (int i = 0; i < 100; i++) {
String data = "" + i;
out.write(data.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 定义方法从管道流中读取数据
public static void readData(PipedInputStream in) {
byte[] bytes = new byte[1024];
try {
// 从管道输入字节流中,读取字节,并保存到字节数组中
int len = in.read(bytes);
while (len != -1) {
// 把bytes数组中从0开始的len个字节转换为字符串并打印
System.out.println(new String(bytes, 0, len));
len = in.read(bytes); // 继续从管道中读取数据
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.4 ThreadLocal 的使用
除了控制资源的访问外, 还可以通过增加资源来保证线程安全.ThreadLocal主要解决的是为每个线程绑定自己的值。
package com.yupeng.threadlocal;
/**
* ThreadLocal的基本使用
* @author Yupeng
* @create 2020-12-09 21:00
*/
public class Test01 {
public static void main(String[] args) {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
}
// 定义ThreadLocal对象
static ThreadLocal threadLocal = new ThreadLocal();
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 设置线程关联的值
threadLocal.set("-->" + i);
// 读取设置的值
System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
}
}
}
}
在多线程环境中,把字符串转换为日期对象,多个线程使用同一个 SimpleDateFormat 对象可能会产生线程安全问题,有异常。
package com.yupeng.threadlocal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 为每个线程指定自己的SimpleDateFormat,使用ThreadLocal
* @author Yupeng
* @create 2020-12-09 21:12
*/
public class Test02 {
public static void main(String[] args) {
// 创建100个线程
for (int i = 0; i < 100; i++) {
new Thread(new ParseDate(i)).start();
}
}
// 定义ThreadLocal对象
static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
// 定义Runnable接口的实现类
static class ParseDate implements Runnable{
private int i;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
String timeStr = "2020-12-22 09:20:" + i % 60; // 定义日期字符串
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
Date date = threadLocal.get().parse(timeStr);
System.out.println(i + "-->" + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}