
典型场景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带来的性能问题,完全可以并行执行的,因为每个线程内都有一个独享的对象,所以不同的线程不会有相互共享的问题,就不会造成线程安全问题。
