典型场景1:每个线程需要一个独享的对象
每个Thread内有自己的实例副本,不共享
比喻:教材只有一本书,一起做笔记有线程安全问题,复印后没问题
如下来看看SimpleDateFormat的进化之路。
模拟:
2个线程分别用自己的SimpleDateFormat
1 . 2个线程分别用自己的SimpleDateFormat,这没问题
public class ThreadLocalNormalUsage00 {
public static void main(String[] args) {
new Thread (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage00 ().date (10);
System.out.println (date);
}
}).start ();
new Thread (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage00 ().date (1007);
System.out.println (date);
}
}).start ();
}
public String date(int seconds){
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000*seconds);
SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
return dateFormat.format(date);
}
}
打印结果如下:
2.后来延伸到30个线程
public class ThreadLocalNormalUsage001 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 30; i++) {
int finalI = i;
new Thread (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage001 ().date (finalI);
System.out.println (date);
}
}).start ();
Thread.sleep (100);
}
}
public String date(int seconds){
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000*seconds);
SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
return dateFormat.format(date);
}
}
3.但是当需求变成了1000个,那么必然要用线程池
1000个打印日期的任务,用线程池来执行
public class ThreadLocalNormalUsage002 {
private static ExecutorService threadPool =
Executors.newFixedThreadPool (10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage002 ().date (finalI);
System.out.println (date);
}
});
}
threadPool.shutdown (); //关闭线程池
}
public String date(int seconds){
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000*seconds);
SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
return dateFormat.format(date);
}
}
4.所有的线程都共用同一个SimpleDateFormat对象,解决了重复创建对象,浪费资源的问题
public class ThreadLocalNormalUsage003 {
private static ExecutorService threadPool =
Executors.newFixedThreadPool (10);
//解决了重复创建对象,浪费资源的问题
static SimpleDateFormat dateFormat = new
SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage003 ().date (finalI);
System.out.println (date);
}
});
}
threadPool.shutdown (); //关闭线程池
}
public String date(int seconds){
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000*seconds);
// SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
return dateFormat.format(date);
}
}
可以看到共用同一个SimpleDateFormat对象的时候,会造成线程不安全问题,出现了并发安全问题。
5.我们可以选择加锁synchronize避免线程不安全问题,但是效率低。
public class ThreadLocalNormalUsage004 {
private static ExecutorService threadPool =
Executors.newFixedThreadPool (10);
//解决了重复创建对象,浪费资源的问题
static SimpleDateFormat dateFormat = new
SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage004 ().date (finalI);
System.out.println (date);
}
});
}
threadPool.shutdown (); //关闭线程池
}
public String date(int seconds){
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000*seconds);
// SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
String s =null;
//加锁避免线程不安全的
synchronized (ThreadLocalNormalUsage004.class){
s = dateFormat.format (date);
}
return s;
}
}
6.在这里更好的解决方案是使用ThreadLocal
/**
* @author: wangxiaobo
* @create: 2021-10-19 17:05
* 1000个打印日期的任务,用线程池来执行
* 利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全。
* 高效利用内存
**/
public class ThreadLocalNormalUsage005 {
private static ExecutorService threadPool =
Executors.newFixedThreadPool (10);
//解决了重复创建对象,浪费资源的问题
// static SimpleDateFormat dateFormat = new
// SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit (new Runnable () {
@Override
public void run() {
String date = new ThreadLocalNormalUsage005 ().date (finalI);
System.out.println (date);
}
});
}
threadPool.shutdown (); //关闭线程池
}
public String date(int seconds) {
//参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
Date date = new Date (1000 * seconds);
SimpleDateFormat simpleDateFormat =
ThreadSafeFormatter.threadLocal.get ();
return simpleDateFormat.format (date);
}
}
class ThreadSafeFormatter{
public static ThreadLocal<SimpleDateFormat> threadLocal =new
ThreadLocal <SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat
("yyyy-MM-dd hh:mm:ss");
}
};
/**
* java 8之后的lambda表达式,和上面的等效
*/
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal
=ThreadLocal.withInitial (()-> new SimpleDateFormat
("yyyy-MM-dd hh:mm:ss"));
}
总结:ThreadLocal是线程安全的
没有synchronized带来的性能问题,完全可以并行执行的,因为每个线程内都有一个独享的对象,所以不同的线程不会有相互共享的问题,就不会造成线程安全问题。
本文含有隐藏内容,请 开通VIP 后查看