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.out.println(Thread.currentThread().getName() +
                                " 设置了值。");

                    } finally {
                        // 溢出变量(防止OOM)
                        threadLocal.remove();
                    }
                }
            });
            Thread.sleep(200);
        }


    }

    // 创建大对象
    static class MyThreadLocal {
        private byte[] bytes = new byte[1 * 1024 * 1024];
    }

ThreadLocal 和普通的Map解决哈希冲突

在这里插入图片描述

单例模式

单例模式

线程通讯(自定义阻塞队列)


    static class MyBlockingQueue {
        private int[] values; // 存放数据的数组
        private int first;    // 队首的下标
        private int last;     // 队尾的下标
        private int size;     // 实际队列大小

        /**
         * 构造方法
         * @param maxSize 最大容量
         */
        public MyBlockingQueue(int maxSize) {
            // 初始化队列
            values = new int[maxSize];
            first = 0;
            last = 0;
            size = 0;
        }

        /**
         * 添加元素(将元素添加到队尾)
         * @param val
         */
        public void offer(int val) throws InterruptedException {
            synchronized (this) {
                // 判断容量是否达到最大值
                if (size == values.length) {
                    // 阻塞等待,消费者先消费
                    this.wait();
                }
                values[last++] = val;
                size++;
                // 判断是否是最后一个元素
                if (last == values.length) {
                    // 循环队列
                    last = 0;
                }
                // 唤醒消费者取队列中的信息
                this.notify();
            }
        }

        /**
         * 取元素(队首元素)
         * @return
         */
        public int poll() throws InterruptedException {
            int result = 0;
            synchronized (this) {
                // 判断队列是否有元素
                if (size == 0) {
                    // 阻塞等待
                    this.wait();
                }
                result = values[first++];
                size--;
                // 判断 first 是否是最后一个元素
                if (first == values.length) {
                    first = 0;
                }
                // 唤醒生产者生产数据
                this.notify();
            }
            return result;
        }
    }

    public static void main(String[] args) {

        MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);

        // 生产者
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

                // 生产数据(添加数据)
                while (true) {
                    int num = new Random().nextInt(10);
                    System.out.println("生产数据:" + num);
                    try {
                        // 生产数据
                        myBlockingQueue.offer(num);
                        // 休息 500 ms
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();

        // 消费者
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int result = myBlockingQueue.poll();
                        System.out.println("消费数据:" + result);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start();

    }

下一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月屯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值