• ThreadLocal的两种典型应用场景


     

    典型场景1:每个线程需要一个独享的对象

                        每个Thread内有自己的实例副本,不共享

                        比喻:教材只有一本书,一起做笔记有线程安全问题,复印后没问题

    如下来看看SimpleDateFormat的进化之路。

    模拟:

          2个线程分别用自己的SimpleDateFormat

    1 . 2个线程分别用自己的SimpleDateFormat,这没问题

    1. public class ThreadLocalNormalUsage00 {
    2. public static void main(String[] args) {
    3. new Thread (new Runnable () {
    4. @Override
    5. public void run() {
    6. String date = new ThreadLocalNormalUsage00 ().date (10);
    7. System.out.println (date);
    8. }
    9. }).start ();
    10. new Thread (new Runnable () {
    11. @Override
    12. public void run() {
    13. String date = new ThreadLocalNormalUsage00 ().date (1007);
    14. System.out.println (date);
    15. }
    16. }).start ();
    17. }
    18. public String date(int seconds){
    19. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    20. Date date = new Date (1000*seconds);
    21. SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    22. return dateFormat.format(date);
    23. }
    24. }

    打印结果如下:

    2.后来延伸到30个线程

    1. public class ThreadLocalNormalUsage001 {
    2. public static void main(String[] args) throws InterruptedException {
    3. for (int i = 0; i < 30; i++) {
    4. int finalI = i;
    5. new Thread (new Runnable () {
    6. @Override
    7. public void run() {
    8. String date = new ThreadLocalNormalUsage001 ().date (finalI);
    9. System.out.println (date);
    10. }
    11. }).start ();
    12. Thread.sleep (100);
    13. }
    14. }
    15. public String date(int seconds){
    16. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    17. Date date = new Date (1000*seconds);
    18. SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    19. return dateFormat.format(date);
    20. }
    21. }

     

    3.但是当需求变成了1000个,那么必然要用线程池

       1000个打印日期的任务,用线程池来执行

    1. public class ThreadLocalNormalUsage002 {
    2. private static ExecutorService threadPool =
    3. Executors.newFixedThreadPool (10);
    4. public static void main(String[] args) throws InterruptedException {
    5. for (int i = 0; i < 1000; i++) {
    6. int finalI = i;
    7. threadPool.submit (new Runnable () {
    8. @Override
    9. public void run() {
    10. String date = new ThreadLocalNormalUsage002 ().date (finalI);
    11. System.out.println (date);
    12. }
    13. });
    14. }
    15. threadPool.shutdown (); //关闭线程池
    16. }
    17. public String date(int seconds){
    18. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    19. Date date = new Date (1000*seconds);
    20. SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    21. return dateFormat.format(date);
    22. }
    23. }

     

    4.所有的线程都共用同一个SimpleDateFormat对象,解决了重复创建对象,浪费资源的问题

    1. public class ThreadLocalNormalUsage003 {
    2. private static ExecutorService threadPool =
    3. Executors.newFixedThreadPool (10);
    4. //解决了重复创建对象,浪费资源的问题
    5. static SimpleDateFormat dateFormat = new
    6. SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    7. public static void main(String[] args) throws InterruptedException {
    8. for (int i = 0; i < 1000; i++) {
    9. int finalI = i;
    10. threadPool.submit (new Runnable () {
    11. @Override
    12. public void run() {
    13. String date = new ThreadLocalNormalUsage003 ().date (finalI);
    14. System.out.println (date);
    15. }
    16. });
    17. }
    18. threadPool.shutdown (); //关闭线程池
    19. }
    20. public String date(int seconds){
    21. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    22. Date date = new Date (1000*seconds);
    23. // SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    24. return dateFormat.format(date);
    25. }
    26. }

     

    可以看到共用同一个SimpleDateFormat对象的时候,会造成线程不安全问题,出现了并发安全问题。

    5.我们可以选择加锁synchronize避免线程不安全问题,但是效率低。

    1. public class ThreadLocalNormalUsage004 {
    2. private static ExecutorService threadPool =
    3. Executors.newFixedThreadPool (10);
    4. //解决了重复创建对象,浪费资源的问题
    5. static SimpleDateFormat dateFormat = new
    6. SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    7. public static void main(String[] args) throws InterruptedException {
    8. for (int i = 0; i < 1000; i++) {
    9. int finalI = i;
    10. threadPool.submit (new Runnable () {
    11. @Override
    12. public void run() {
    13. String date = new ThreadLocalNormalUsage004 ().date (finalI);
    14. System.out.println (date);
    15. }
    16. });
    17. }
    18. threadPool.shutdown (); //关闭线程池
    19. }
    20. public String date(int seconds){
    21. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    22. Date date = new Date (1000*seconds);
    23. // SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    24. String s =null;
    25. //加锁避免线程不安全的
    26. synchronized (ThreadLocalNormalUsage004.class){
    27. s = dateFormat.format (date);
    28. }
    29. return s;
    30. }
    31. }

         

         

    6.在这里更好的解决方案是使用ThreadLocal

    1. /**
    2. * @author: wangxiaobo
    3. * @create: 2021-10-19 17:05
    4. * 1000个打印日期的任务,用线程池来执行
    5. * 利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全。
    6. * 高效利用内存
    7. **/
    8. public class ThreadLocalNormalUsage005 {
    9. private static ExecutorService threadPool =
    10. Executors.newFixedThreadPool (10);
    11. //解决了重复创建对象,浪费资源的问题
    12. // static SimpleDateFormat dateFormat = new
    13. // SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
    14. public static void main(String[] args) throws InterruptedException {
    15. for (int i = 0; i < 1000; i++) {
    16. int finalI = i;
    17. threadPool.submit (new Runnable () {
    18. @Override
    19. public void run() {
    20. String date = new ThreadLocalNormalUsage005 ().date (finalI);
    21. System.out.println (date);
    22. }
    23. });
    24. }
    25. threadPool.shutdown (); //关闭线程池
    26. }
    27. public String date(int seconds) {
    28. //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
    29. Date date = new Date (1000 * seconds);
    30. SimpleDateFormat simpleDateFormat =
    31. ThreadSafeFormatter.threadLocal.get ();
    32. return simpleDateFormat.format (date);
    33. }
    34. }
    35. class ThreadSafeFormatter{
    36. public static ThreadLocal<SimpleDateFormat> threadLocal =new
    37. ThreadLocal <SimpleDateFormat>(){
    38. @Override
    39. protected SimpleDateFormat initialValue() {
    40. return new SimpleDateFormat
    41. ("yyyy-MM-dd hh:mm:ss");
    42. }
    43. };
    44. /**
    45. * java 8之后的lambda表达式,和上面的等效
    46. */
    47. public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal
    48. =ThreadLocal.withInitial (()-> new SimpleDateFormat
    49. ("yyyy-MM-dd hh:mm:ss"));
    50. }

    总结:ThreadLocal是线程安全的

    没有synchronized带来的性能问题,完全可以并行执行的,因为每个线程内都有一个独享的对象,所以不同的线程不会有相互共享的问题,就不会造成线程安全问题。

     

  • 相关阅读:
    基于纳芯微产品的尾灯方案介绍
    nginx的配置
    使用 HTML CSS 和 JavaScript 创建星级评分系统
    【操作系统】7/35进程原语2
    【Linux】基础IO —— 下(实现动静态库)
    全国各地行业网站SEO词系统+正版授权+SEO优化
    从0手写两轮差速机器人urdf模型
    2013 ~【VUE+ ElementUI】——【上传、下载】进度计算
    springboot实现支付宝支付功能
    产品思维训练 | 试着将知乎分别介绍给你的爸爸、妈妈并下载使用
  • 原文地址:https://blog.csdn.net/qq_34709784/article/details/126397145