→ ThreadLocal
ThreadLocal是什么?
从名字可以看到ThreadLocal是线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
使用场景:
1、进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,session会话管理。
ThreadLocal怎么用?
验证一下:
public class ThreadLocalTest{public static void main(String[] args){// 创建对象ThreadLocal<String> local = new ThreadLocal();// 新建一个随机数类Random random = new Random();// 使用Java8的Stream新建5个线程IntStream.range(0,5).forEach(a -> new Thread(() -> {//为每一个线程设置相应的local值local.set(a + " " + random.nextInt(10));System.out.println("线程和local值分别是:" + local.get());try{TimeUnit.SECONDS,sleep(1);}catch(InterruptedException e){e.printStackTrace();}}).start());}}/* 线程和local值分别是:0 6线程和local值分别是:1 4线程和local值分别是:2 3线程和local值分别是:4 9线程和local值分别是:3 5*/
从结果我们可以看到,每一个线程都有各自的local值,我们设置了一个休眠时间,就是为了另外一个线程也能够及时的读取当前的local值。
所以ThreadLocal在数据库连接场景中常用,比如一个客户端频繁的使用数据库,就需要建立多次连接和关闭,我们的服务器可能吃不消,这时候最好使用ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何时候都可以使用,线程之间互不影响,这样就不存在线程安全问题,也不会严重影响程序执行性能。
ThreadLocal源码分析
(1)每个Tread维护着一个ThreadLocalMap的引用。
(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来存储。
(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中。
(5)在进场get之前,必须先进行set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
(6)ThreadLocal本身并不存储值,它只是作为一个key让线程从ThreadLocalMap中获取value。
对于ThreadLocal来说关键就是内部的ThreadLocalMap。
ThreadLocal注意要点——内存泄漏
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
→ 有三个线程T1,T2,T3,如何保证顺序执行
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。
Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞 状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
→ 写一个死锁的程序
写一个简单的死锁程序:
public class LockDemo{public static void main(String[] args){new Thread(()-> {try{System.out.println("Thread1 is running");synchronized(LockDemo.class){System.out.println("thread is block obj1");Thread.sleep(3000);synchronized(Object.class){System.out.println("thread is block ojb2");}}}catch(Exception e){e.printStackTrace();}}).start();new Thread(()-> {try{System.out.println("Thread2 is running");synchronized(Object.class){System.out.println("thread2 is block obj1");Thread.sleep(3000);synchronized(LockDemo.class){System.out.println("thread2 is block obj2");}}}catch(Exception e){e.printStackTrace();}}).start();}}
结果分析:当程序开始执行时,创建两个线程,分别执行不同的synchronized 方法块;并且都在Thread.sleep(1000);处停下来,此时当线程1先sleep结束后;准备用Main.class开 synchronized 这把锁进入 synchronized (Main.class) 方法块,但是发现线程2还没有苏醒,所以 线程1就等着线程2执行完毕,释放出 Main.class 钥匙,这时候 线程2苏醒了,准备拿String.class 这把钥匙,去开synchronized 这把锁,进入synchronized (String.class) 方法块;但是发现线程1 也在等着 线程2释放 Main.class钥匙,所以就导致线程1等着线程2 释放 Main.class钥匙, 线程2等着线程1 释放 String.class 这把钥匙,所以这个程序就僵持住了,成了一个死锁的程序。
【参考链接】https://blog.csdn.net/weixin_41528317/article/details/83063994
.
→ 写代码来解决生产者消费者问题
一个示例demo。
资源:
/*** 资源*/class Clerk{// 当前资源数量private int product = 0;// 创建锁对象private Lock lock = new ReentrantLock();private Condition = new lock.newCondition();// 生产资源public void get(){lock.lock();try{// 资源满了就进入阻塞状态 jdk建议使用while循环 避免虚假唤醒 所以要再次确认while(product >=1){System.out.println("产品已满");try{System.out.println("生产者进入等待");condition.await();}catch(InterruptedException e){e.printStckTrace();}}System.out.println(Thread.currentThread().getName()+ ":" + ++product);condition.singleAll();}finally{lock.unlock();}}// 消费资源public void sale(){// 开启锁lock.lock();try{while(product <= 0){System.out.println("缺货");try{condition.await();}catch(InterruptedException e){e.printStckTrace();}}System.out.println(Thread.currentThread().getName()+ ":" + --product)condition.singleAll();}finally{lock.unlock();}}}
生产者:
/*** 生产者*/class Producer implements Runnable{private Clerk clerk;public Producer (Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}clerk.get();}}}
消费者:
class Consumer implements Runnable{private Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {clerk.sale();}}}
测试类:
public class TestProductorAndConsumer {public static void main(String[] args) {Clerk clerk = new Clerk();Producer producer = new Producer(clerk);Consumer consumer = new Consumer(clerk);new Thread(producer, "生产者A").start();new Thread(consumer, "消费者B").start();new Thread(producer, "生产者C").start();new Thread(consumer, "消费者D").start();}}
