彻底搞懂Java多线程(四)

目录
  • SimpleDateFormat非线程安全问题
  • ThreadLocal
    • ThreadLocal的原理
    • ThreadLocal常用方法
    • ThreadLocal的初始化
    • InheritableThreadLocal的使用
  • 总结

SimpleDateFormat非线程安全问题

实现1000个线程的时间格式化

package SimpleDateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 17:30;
 */
public class SimpleDateFormat1 {
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,100,
                TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 1001; i++) {
            int finalI = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Date date = new Date(finalI * 1000);
                    myFormatTime(date);
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
    private static void myFormatTime(Date date){
        System.out.println(simpleDateFormat.format(date));
    }
}

产生了线程不安全的问题👇:

这是因为:

多线程的情况下:

线程1在时间片用完之后,线程2来setTime()那么线程1的得到了线程2的时间。

所以可以使用加锁的操作:

就不会有重复的时间了

但是虽然可以解决线程不安全的问题,但是排队等待锁,性能就会变得低

所以可以使用局部变量:

也解决了线程不安全的问题:

但是每次也都会创建新的私有变量

那么有没有一种方案既可以避免加锁排队执行,又不会每次创建任务的时候不会创建私有的变量呢?

那就是ThreadLocal👇:

ThreadLocal

ThreadLocal的作用就是让每一个线程都拥有自己的变量。

那么选择锁还是ThreadLocal?

看创建实列对象的复用率,如果复用率比较高的话,就使用ThreadLocal。

ThreadLocal的原理

类ThreadLocal的主要作用就是将数据放到当前对象的Map中,这个Map时thread类的实列变量。类ThreadLocal自己不管理、不存储任何的数据,它只是数据和Map之间的桥梁。

执行的流程:数据—>ThreadLocal—>currentThread()—>Map。

执行后每个Map存有自己的数据,Map中的key中存储的就是ThreadLocal对象,value就是存储的值。每个Thread的Map值只对当前的线程可见,其它的线程不可以访问当前线程对象中Map的值。当前的线程被销毁,Map也随之被销毁,Map中的数据如果没有被引用、没有被使用,则随时GC回收。

ThreadLocal常用方法

set(T):将内容存储到ThreadLocal

get():从线程去私有的变量

remove():从线程中移除私有变量

package ThreadLocalDemo;
import java.text.SimpleDateFormat;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 18:37;
 */
public class ThreadLocalDemo1 {
    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        //设置私有变量
        threadLocal.set(new SimpleDateFormat("mm:ss"));
        //得到ThreadLocal
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        //移除
        threadLocal.remove();
    }
}

ThreadLocal的初始化

ThreadLocal提供了两种初始化的方法

initialValue()和

initialValue()初始化:

package ThreadLocalDemo;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 19:07;
 */
public class ThreadLocalDemo2 {
    //创建并初始化ThreadLocal
    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal() {
        @Override
        protected SimpleDateFormat initialValue() {
            System.out.println(Thread.currentThread().getName() + "执行了自己的threadLocal中的初始化方法initialValue()");
            return new SimpleDateFormat("mm:ss");
        }
    };
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            Date date = new Date(5000);
            System.out.println("thread0格式化时间之后得结果时:" + threadLocal.get().format(date));
        });
        thread1.setName("thread0");
        thread1.start();

        Thread thread2 = new Thread(() -> {
            Date date = new Date(6000);
            System.out.println("thread1格式化时间之后得结果时:" + threadLocal.get().format(date));
        });
        thread2.setName("thread1");
        thread2.start();
    }
}

withInitial方法初始化:

package ThreadLocalDemo;
import java.util.function.Supplier;
/**
 * user:ypc;
 * date:2021-06-14;
 * time: 17:23;
 */
public class ThreadLocalDemo3 {
    private static ThreadLocal<String> stringThreadLocal =
            ThreadLocal.withInitial(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println("执行了withInitial()方法");
                    return "我是" + Thread.currentThread().getName() + "的ThreadLocal";
                }
            });
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println(stringThreadLocal.get());
        });
        thread1.start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(stringThreadLocal.get());
            }
        });
        thread2.start();
    }
}

注意:

ThreadLocal如果使用了set()方法的话,那么它的初始化方法就不会起作用了。

来看:👇

package ThreadLocalDemo;
/**
 * user:ypc;
 * date:2021-06-14;
 * time: 18:43;
 */
class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("在ThreadA中取值:" + Tools.t1.get());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadLocalDemo4 {
    public static void main(String[] args) throws InterruptedException {
        //main是ThreadA 的 父线程 让main线程set,ThreadA,是get不到的
        if (Tools.t1.get() == null) {
            Tools.t1.set("main父线程的set");
        }
        System.out.println("main get 到了: " + Tools.t1.get());

        Thread.sleep(1000);
        ThreadA a = new ThreadA();
        a.start();
    }
}

类ThreadLocal不能实现值的继承,那么就可以使用InheritableThreadLocal了👇

InheritableThreadLocal的使用

使用InheritableThreadLocal可以使子线程继承父线程的值

在来看运行的结果:

子线程有最新的值,父线程依旧是旧的值

package ThreadLocalDemo;
/**
 * user:ypc;
 * date:2021-06-14;
 * time: 19:07;
 */
class ThreadB extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("在ThreadB中取值:" + Tools.t1.get());
            if (i == 5){
                Tools.t1.set("我是ThreadB中新set()");
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadLocalDemo5 {
    public static void main(String[] args) throws InterruptedException {
        if (Tools.t1.get() == null) {
            Tools.t1.set("main父线程的set");
        }
        System.out.println("main get 到了: " + Tools.t1.get());

        Thread.sleep(1000);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        for (int i = 0; i < 10; i++) {
            System.out.println("main的get是:" + Tools.t1.get());
            Thread.sleep(100);
        }
    }
}

ThreadLocal的脏读问题来看👇

package ThreadLocalDemo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * user:ypc;
 * date:2021-06-14;
 * time: 19:49;
 */
public class ThreadLocalDemo6 {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private static class MyThread extends Thread {
        private static boolean flag = false;
        @Override
        public void run() {
            String name = this.getName();
            if (!flag) {
                threadLocal.set(name);
                System.out.println(name + "设置了" + name);
                flag = true;
            }
            System.out.println(name + "得到了" + threadLocal.get());
        }
    }
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));

        for (int i = 0; i < 2; i++) {
            threadPoolExecutor.execute(new MyThread());
        }
        threadPoolExecutor.shutdown();
    }
}

发生了脏读:

线程池复用了线程,也复用了这个线程相关的静态属性,就导致了脏读

那么如何避免脏读呢?

去掉static 之后:

总结

本篇文章就到这里了,希望对你有些帮助,也希望你可以多多关注我们的更多内容!

(0)

相关推荐

  • 彻底搞懂Java多线程(三)

    目录 Java线程池 线程池的优点 线程池的6种创建方式 创建单个线程池的作用是什么? 线程池的第七种创建方式 ThreadPoolExecutor的执行方式 ThreadPoolExecutor的执行流程 线程池的终止 线程池的状态 异步.同步 1.Java 线程 同步与异步 线程工厂 总结 Java线程池 线程的缺点: 1.线程的创建它会开辟本地方法栈.JVM栈.程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源. 2.在任务量远远大于线程可

  • 彻底搞懂Java多线程(一)

    目录 Java多线程 线程的创建 线程常用方法 线程的终止 1.自定义实现线程的终止 2.使用Thread的interrupted来中断 3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别 线程的状态 线程的优先级 守护线程 线程组 线程安全问题 volatile关键字 总结 Java多线程 线程的创建 1.继承Thread 2.实现Runnable 3.实现Callable 使用继承Thread类来开发多线程的应用程序

  • 彻底搞懂Java多线程(五)

    目录 单例模式与多线程 立即加载/饿汉模式 延时加载/懒汉模式 饿汉/懒汉对比 阻塞队列的实现 常见的锁策略 乐观锁 CAS CAS在java中的应用 CAS 的ABA问题 ABA 问题的解决 悲观锁 独占锁.共享锁.自旋锁.可重入锁 详解synchronized锁的优化问题 Semaphore Semaphore的作用: Semaphore实现原理: Semaphore的使用: CountDownLatch\CyclicBarrier CountDownLatch CountDownLatch

  • 彻底搞懂Java多线程(二)

    目录 Java中的锁 1.synchronized锁(jvm层的解决方案,也叫监视器锁) 2.手动锁Lock synchronized锁 synchronized使用场景 1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作) 2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作) 3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁) synchronized注意事项 1.加锁的时候一定要使用同一把锁对象 Lock锁使用的注意事项

  • 彻底搞懂Java多线程(四)

    目录 SimpleDateFormat非线程安全问题 ThreadLocal ThreadLocal的原理 ThreadLocal常用方法 ThreadLocal的初始化 InheritableThreadLocal的使用 总结 SimpleDateFormat非线程安全问题 实现1000个线程的时间格式化 package SimpleDateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java

  • 一文彻底搞懂java多线程和线程池

    目录 什么是线程 一. Java实现线程的三种方式 1.1.继承Thread类 1.2.实现Runnable接口,并覆写run方法 二. Callable接口 2.1 Callable接口 2.2 Future接口 2.3 Future实现类是FutureTask. 三. Java线程池 3.1.背景 3.2.作用 3.3.应用范围 四. Java 线程池框架Executor 4.1.类图: 4.2 核心类ThreadPoolExecutor: 4.3 ThreadPoolExecutor逻辑结

  • Java基础:彻底搞懂java多线程

    目录 进程与线程 使用多线程的优势 线程的状态 创建线程 线程中断 总结 进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位.进程可以被看作程序的实体,同样,它也是程序的容器. 线程 线程是操作系统调度的最小单元,也叫作轻量级进程.在一个进程中可以创建多个线程,这些线程都拥有各自的计数器.堆栈和局部变量等属性. 使用多线程的优势 使用多线程可以减少程序的响应时间 如果某个操作很耗时,或者陷入长时间的等待,此时程序将不会响应鼠标和键盘等

  • 一文搞懂Java创建线程的五种方法

    目录 题目描述 解题思路 代码详解 第一种 继承Thread类创建线程 第二种:实现Runnable接口创建线程 第三种:实现Callable接口,通过FutureTask包装器来创建Thread线程 第四种:使用ExecutorService.Callable(或者Runnable).Future实现返回结果的线程 第五种:使用ComletetableFuture类创建异步线程,且是据有返回结果的线程 题目描述 Java创建线程的几种方式 Java使用Thread类代表线程,所有线程对象都必须

  • 一文搞懂Java并发AQS的共享锁模式

    目录 概述 自定义共享锁例子 核心原理机制 源码解析 成员变量 共享锁获取acquireShared(int) 共享释放releaseShared(int) 概述 这篇文章深入浅出理解Java并发AQS的独占锁模式讲解了AQS的独占锁实现原理,那么本篇文章在阐述AQS另外一个重要模式,共享锁模式,那什么是共享锁呢? 共享锁可以由多个线程同时获取, 比较典型的就是读锁,读操作并不会产生副作用,所以可以允许多个线程同时对数据进行读操作而不会有线程安全问题,jdk中的很多并发工具比如ReadWrite

  • 一文搞懂JAVA 修饰符

    Java语言提供了很多修饰符,主要分为以下两类: 访问修饰符 非访问修饰符 修饰符用来定义类.方法或者变量,通常放在语句的最前端.我们通过下面的例子来说明: public class ClassName { // ... } private boolean myFlag; static final double weeks = 9.5; protected static final int BOXWIDTH = 42; public static void main(String[] argum

随机推荐