ThreadLocal单例模式线程通讯bing

Posted 鸟随二月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal单例模式线程通讯bing相关的知识,希望对你有一定的参考价值。

上一篇

线程安全问题

场景:实现1000任务的时间格式化

   private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        // 定义线程池
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(1000));
        for (int i = 1; i < 1001; i++) {
            final int finalI = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Date date = new Date(finalI * 1000);
                    myFormatTime(date);
                }
            });
        }
    }

    private static void myFormatTime(Date date) {
        String result = simpleDateFormat.format(date);
        System.out.println("时间:" + result);
    }

结果

在这里插入图片描述

原因

在这里插入图片描述
异常情况
在这里插入图片描述

解决方式修改部分代码

1.加锁( synchronized )

 private synchronized static void myFormatTime(Date date) {
        String result = simpleDateFormat.format(date);
        System.out.println("时间:" + result);
    }

2.私有变量

 private static void myFormatTime(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        String result = simpleDateFormat.format(date);
        System.out.println("时间:" + result);
    }

3.ThreadLocal 一种方案既可以避免加锁排队执行,又不会每次执行任务都需要重新创建私有变量的方法

ThreadLocal线程的本地变量,每个线程创建一个私有变量,以1000个任务10个线程池的示例来说,使用ThreadLocal就是创建10 SimpleDateFormat对象。
在这里插入图片描述

基本使用

   // 创建 ThreadLocal
    private static ThreadLocal<String> threadLocal =
            new ThreadLocal<>();

    public static void main(String[] args) {
        // 定义公共任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 得到线程名称
                String tname = Thread.currentThread().getName();
                System.out.println(tname + " 设置值:" + tname);
                try {
                    // set ThreadLocal
                    threadLocal.set(tname);
                    // 执行 ThreadLocal 打印
                    printThreaLocal();
                } finally {
                    // 移除 ThreadLocal
                    threadLocal.remove();
                }
            }
        };

        Thread t1 = new Thread(runnable, "线程1");
        t1.start();

        Thread t2 = new Thread(runnable, "线程2");
        t2.start();

    }

    private static void printThreaLocal() {
        // 从 ThreadLocal 中获取值
        String result = threadLocal.get();
        System.out.println(Thread.currentThread().getName() +
                " 中取值:" + result);
    }

初始化使用

  // 创建和初始化 ThreadLocal
    private static ThreadLocal<SimpleDateFormat> threadLocal =
            new ThreadLocal() {
                @Override
                protected SimpleDateFormat initialValue() {//返回值与该类泛型化一致
                    System.out.println("执行 InitialValue 方法");
                    return new SimpleDateFormat("mm:ss");
                }
            };

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Date date = new Date(1000);
                // 从 ThreadLocal 获取 DateFormat 对象,并格式化时间
                String result = threadLocal.get().format(date);
                System.out.println("线程1 时间格式化:" + result);
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                Date date = new Date(2000);
                // 从 ThreadLocal 获取 DateFormat 对象,并格式化时间
                String result = threadLocal.get().format(date);
                System.out.println("线程2 时间格式化:" + result);
            }
        });
        t2.start();
    }

线程池使用

    private static ThreadLocal<Integer> threadLocal =
            new ThreadLocal() {
                @Override
                protected Integer initialValue() {
                    int num = new Random().nextInt(10);
                    System.out.println("执行了 initialValue 生成了:" + num);
                    return num;
                }
            };

    public static void main(String[] args) {
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, 1,
                0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000));

        executor.submit(new Runnable() {
            @Override
            public void run() {
                // get ThreadLocal
                int result = threadLocal.get();
                System.out.println(Thread.currentThread().getName() +
                        " 得到结果1:" + result);
            }
        });

        executor.submit(new Runnable() {
            @Override
            public void run() {
                // get ThreadLocal
                int result = threadLocal.get();
                System.out.println(Thread.currentThread().getName() +
                        " 得到结果2:" + result);
            }
        });

    }

另一种用法ThreadLocal.withInitial

  // 创建并初始化 ThreadLocal
    private static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println("执行了 withInitial 方法");
                    return Thread.currentThread().getName() + "Java";
                }
            });

    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 获取 ThreadLoal
                String result = threadLocal.get();
                System.out.println(Thread.currentThread().getName() +
                        " 获取到的内容:" + result);
            }
        };
        Thread t1 = new Thread(runnable, "线程1");
        t1.start();

        Thread t2 = new Thread(runnable, "线程2");
        t2.start();

    }

简洁版:

    private static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(() -> "Java");

当ThreadLocal中出现set 方法之后,所有类型的初始化方法就不会执行了。

原因:ThreadLocal在执行get方法的时候,才去判断并调用初始化方法。
当调用ThreadLocal.get()方法时
在这里插入图片描述

ThreadLocal的使用场景

在这里插入图片描述


    private static ThreadLocal<User> userThreadLocal
            = new ThreadLocal();

    /**
     * 实体类
     */
    static class User {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) {
        Storage storage = new Storage();
        Storage2 storage2 = new Storage2();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 用户登录
                User user = new User();
                user.setName("比特");
                userThreadLocal.set(user);

                // 打印用户的信息
                storage.printUserName3();
                storage2.printUserName3();
            }
        });
        t1.start();

    }


    class UserStrorage {

        public User getUser() {
            // 登录
            User user = new User();
            user.setName("比特");
            return user;
        }

    }

    /**
     * 仓储类
     */
    static class Storage {
//        public void printUserName(User user) {
//            System.out.println(user.getName());
//        }
//
//        public void printUserName2() {
//            System.out.println(new UserStrorage()
//                    .getUser().getName());
//        }

        public void printUserName3() {
            User user = userThreadLocal.get();
            System.out.println("用户名" + user.getName());
        }
    }

    /**
     * 类 2
     */
    static class Storage2 {
        public void printUserName3() {
            User user = userThreadLocal.get();
            System.out.println("用户名" + user.getName());
        }
    }

ThreadLocal的问题

不可继承性

在子进程里访问不到父进程的变量

解决方案

使用ThreadLocal的子类

 // 创建 ThreadLocal
    private static ThreadLocal threadLocal =
            new InheritableThreadLocal();

    public static void main(String[] args) {
        // 在主线程里面设置值
        threadLocal.set("Java");

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("获取值:" +
                        threadLocal.get());
            }
        });
        t1.start();
    }

脏读问题

在一个线程中读取到了不属于自己的信息就叫做脏读

   static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, 1,
                0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

        for (int i = 0; i < 2; i++) {
            executor.submit(new MyThreadLocal());
        }

//        MyThreadLocal t1 = new MyThreadLocal();
//        t1.start();
//
//        MyThreadLocal t2 = new MyThreadLocal();
//        t2.start();
    }

    static class MyThreadLocal extends Thread {
        private static boolean flag = false;

        @Override
        public void run() {
            String tname = this.getName();
//            String tname = Thread.currentThread().getName();
            if (!flag) {
                // 第一次执行
                threadLocal.set(tname);
                System.out.println(tname + " 设置了:" + tname);
                flag = true;
            }
            System.out.println(tname + "得到了:" + threadLocal.get());
        }
    }

结果
在这里插入图片描述
脏读产生的原因:
线程池复用了线程,和这个线程相关的静态属性也复用,所以就导致了脏读。

解决方案:

a)避免使用静态变量。
b)使用完之后,执行remove操作。(threadLocal.remove();)

内存溢出

当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就是内存溢出。(使用线程池容易出现该情况)
内存溢出的原因:
1.线程池是长声明周期。
垃圾回收器就不会回收
2.Thread -> ThreadLocalMap ->Entry key, value(1mb资源)
value资源。

在这里插入图片描述
在这里插入图片描述
ThreadLocal 会将key设置为弱引用是因为Threadlocal 为了更大程度的避免OOM

解决方案

// 创建 ThreadLocal
    private static ThreadLocal<MyThreadLocal> threadLocal =
            new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                10, 10, 0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000)
        );
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        MyThreadLocal myThreadLocal = new MyThreadLocal();
                        threadLocal.set(myThreadLocal);
                        System.以上是关于ThreadLocal单例模式线程通讯bing的主要内容,如果未能解决你的问题,请参考以下文章

“单例”模式-ThreadLocal线程单例

单例模式/ThreadLocal/线程内共享数据

ThreadLocal和单例对象比较

Spring应用中的ThreadLocal VS synchronized

C++ 线程安全的单例模式总结

既然spring中注入用单例,为了解决多线程安全问题,还得用theardlocal为每个线程创建共