→ ThreadLocal

ThreadLocal是什么?
从名字可以看到ThreadLocal是线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
使用场景:
1、进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,session会话管理。
ThreadLocal怎么用?
验证一下:

  1. public class ThreadLocalTest{
  2. public static void main(String[] args){
  3. // 创建对象
  4. ThreadLocal<String> local = new ThreadLocal();
  5. // 新建一个随机数类
  6. Random random = new Random();
  7. // 使用Java8的Stream新建5个线程
  8. IntStream.range(0,5).forEach(a -> new Thread(() -> {
  9. //为每一个线程设置相应的local值
  10. local.set(a + " " + random.nextInt(10));
  11. System.out.println("线程和local值分别是:" + local.get());
  12. try{
  13. TimeUnit.SECONDS,sleep(1);
  14. }catch(InterruptedException e){
  15. e.printStackTrace();
  16. }
  17. }).start());
  18. }
  19. }
  20. /* 线程和local值分别是:0 6
  21. 线程和local值分别是:1 4
  22. 线程和local值分别是:2 3
  23. 线程和local值分别是:4 9
  24. 线程和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注意要点——内存泄漏
Java多线程-ThreadLocal - 图1
上面这张图详细的揭示了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 的宠幸。

.

→ 写一个死锁的程序

写一个简单的死锁程序:

  1. public class LockDemo{
  2. public static void main(String[] args){
  3. new Thread(()-> {
  4. try{
  5. System.out.println("Thread1 is running");
  6. synchronized(LockDemo.class){
  7. System.out.println("thread is block obj1");
  8. Thread.sleep(3000);
  9. synchronized(Object.class){
  10. System.out.println("thread is block ojb2");
  11. }
  12. }
  13. }catch(Exception e){
  14. e.printStackTrace();
  15. }
  16. }).start();
  17. new Thread(()-> {
  18. try{
  19. System.out.println("Thread2 is running");
  20. synchronized(Object.class){
  21. System.out.println("thread2 is block obj1");
  22. Thread.sleep(3000);
  23. synchronized(LockDemo.class){
  24. System.out.println("thread2 is block obj2");
  25. }
  26. }
  27. }catch(Exception e){
  28. e.printStackTrace();
  29. }
  30. }).start();
  31. }
  32. }

结果分析:当程序开始执行时,创建两个线程,分别执行不同的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。
资源:

  1. /**
  2. * 资源
  3. */
  4. class Clerk{
  5. // 当前资源数量
  6. private int product = 0;
  7. // 创建锁对象
  8. private Lock lock = new ReentrantLock();
  9. private Condition = new lock.newCondition();
  10. // 生产资源
  11. public void get(){
  12. lock.lock();
  13. try{
  14. // 资源满了就进入阻塞状态 jdk建议使用while循环 避免虚假唤醒 所以要再次确认
  15. while(product >=1){
  16. System.out.println("产品已满");
  17. try{
  18. System.out.println("生产者进入等待");
  19. condition.await();
  20. }catch(InterruptedException e){
  21. e.printStckTrace();
  22. }
  23. }
  24. System.out.println(Thread.currentThread().getName()+ ":" + ++product);
  25. condition.singleAll();
  26. }finally{
  27. lock.unlock();
  28. }
  29. }
  30. // 消费资源
  31. public void sale(){
  32. // 开启锁
  33. lock.lock();
  34. try{
  35. while(product <= 0){
  36. System.out.println("缺货");
  37. try{
  38. condition.await();
  39. }catch(InterruptedException e){
  40. e.printStckTrace();
  41. }
  42. }
  43. System.out.println(Thread.currentThread().getName()+ ":" + --product)
  44. condition.singleAll();
  45. }finally{
  46. lock.unlock();
  47. }
  48. }
  49. }

生产者:

  1. /**
  2. * 生产者
  3. */
  4. class Producer implements Runnable{
  5. private Clerk clerk;
  6. public Producer (Clerk clerk){this.clerk = clerk;}
  7. @Override
  8. public void run() {
  9. for (int i = 0; i < 20; i++) {
  10. try {
  11. Thread.sleep(200);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. clerk.get();
  16. }
  17. }
  18. }

消费者:

  1. class Consumer implements Runnable{
  2. private Clerk clerk;
  3. public Consumer(Clerk clerk){
  4. this.clerk = clerk;
  5. }
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 20; i++) {
  9. clerk.sale();
  10. }
  11. }
  12. }

测试类:

  1. public class TestProductorAndConsumer {
  2. public static void main(String[] args) {
  3. Clerk clerk = new Clerk();
  4. Producer producer = new Producer(clerk);
  5. Consumer consumer = new Consumer(clerk);
  6. new Thread(producer, "生产者A").start();
  7. new Thread(consumer, "消费者B").start();
  8. new Thread(producer, "生产者C").start();
  9. new Thread(consumer, "消费者D").start();
  10. }
  11. }