→ 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;}
@Override
public 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;
}
@Override
public 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();
}
}