紫玉坏小子

导航

多线程简学

多线程学习

线程相关概念

进程:是计算机中程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。简单的理解就是操作系统中正在运行的一个程序。

线程:就是进程的一个执行单元,一个单一顺序的控制流。一个进程至少有一个线程(main函数),进程是线程的容器。每个线程都有自己的线程栈,自己的寄存器环境,自己的线程本地存储。

主线程:JVM启动时会创建一个主线程,该主线程负责执行main方法,主线程就是执行main方法的线程,线程之间存在父子关系,子线程和父线程 属于包含和被包含

串行、并行、并发关系图

 

 

 

 

并行可以理解为严格更理想的并发

在java中,创建一个线程就是创建一个Thread类(子类)的对象(实例)

Thread类有两个常用的构造方法:Thread()与Thread(Runnable)

定义Thread子类

public class ThreadDemo extends  Thread {
   @Override
   public void run() {
       for (int i = 0; i < 10; i++) {
           try {
               Thread.sleep(1000);
               System.out.println(i);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
}

定义一个Runnable接口的实现类

public class RunnableDemo  implements Runnable{
   @Override
   public void run() {
       for (int i = 0; i < 10; i++) {
           try {
               Thread.sleep(1000);
               System.out.println(i);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
}

这俩种创建线程的实质没有区别

主线程调用

 public static void main(String[] args) {
       ThreadDemo basic = new ThreadDemo();
       basic.start();
       new Thread(new RunnableDemo()).start();
  }

start()方法来启动线程,但是start()的调用顺序并不一定是一定的,是线程调度器来分配执行顺序

线程的设置名称和获取名称 名称的作用主要是为了测试时使用

Thread.currentThread().getName() 获取线程名称
Thread.currentThread().setName("t1") 设置线程名称

isAlive方法测试线程活动状态

boolean alive = basic.isAlive();判断线程是否是激活状态

sleep()方法 让当前线程休眠毫秒数

 Thread.sleep(1100);

getId()可以获得线程的唯一标识

 long id = basic.getId();

yield()方法的作用是放弃当前CPU资源

 basic.yield();
basic.setPriority(5); 设置线程的优先值  1到10
 basic.setDaemon(true);守护线程  主线程结束后 子线程也会停止

线程的生命周期是线程对象的生老病死,即线程的状态,线程的生命周期可以通过getState()方法获得,线程的状态是枚举类型定义的 具体的可以查看javaApi Enum Thread.State

public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,

/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

线程安全问题

非线程安全:主要是指多个线程对同一个对象的实例变量进行操作,会出现值被更改,值不同步的情况。

原子性:就是不可分割的意思,多个线程对共享变量进行操作时,要么已经开始 要么已经结束

访问(读写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,要么尚未发生,即其他线程到当前操作的中间结果,如从ATM机取款

class MyInt{
//java中提供了一个线程安全的AtomicInteger类 ,保证了操作的原子性
AtomicInteger integer = new AtomicInteger();
public int getInt(){
return integer.getAndIncrement();
}

可见性:一个线程对某个共享变量进行更新,后续其他的线程可能无法立即读到这个更新的结果,这就是线程安全问题的另外一种形式。

如果一个线程对共享变量更新后,后续访问该线程的其他线程可以读到更新的结果,称这个线程对共享变量的更新对其他线程可见。

java内存

 

锁概述

线程安全问题就是多个线程并发访问共享数据

将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问。

锁可以理解为对共享数据进行保护的一个许可证,任何线程想访问这些共享数据必须先持有该许可证。访问后释放其持有的锁(许可证)

锁具有排他性,即一个锁一次只能被一个线程使用。

 

JVM把锁分为内部锁和显示锁,内部锁通过synchronized关键字实现,显示锁通过java.concurrent.locks.Lock接口来实现

锁的作用

锁可以实现对共享数据的安全访问,保障线程的原子性、可见性和有序性

锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保障临界区的代码一次只能被一个线程执行。

锁的可重入性

如果一个线程持有一个锁的时候还能够继续申请该锁,称该锁是可重入的

void methodA(){
//申请A锁
methodB();
//释放A锁
}
void methodB(){
//申请A锁
.....
//释放A锁
}

内部锁:java中每个对象都有一个与之关联的内部锁(Intrinsic Lock) 这种锁也称为监视器。这种内部锁是一种排它锁,可以保障原子性。

内部锁是通过synchronized关键字实现的

修饰代码块:

synchronized(对象锁){
同步代码块,可以在同步代码块中访问共享数据
}

修饰方法

public void synchronized method(){
代码块
}
public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj.mm()).start();
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}

锁对象不同不能实现同步

public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
SynchronizedClass obj1 = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj1.mm()).start();
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}

使用常量作为锁对象

public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
SynchronizedClass obj1 = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj1.mm()).start();
}
private static final Object obj = new Object();
public void mm(){
synchronized (obj){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}

 

到底多线程 是怎么学的 坚持不下来 有点烦恼!!!!

今天下午先学习5章!

第一章

多线程的基础

首先理解一点:多线程是同时发生的么?

不是,只是cpu把工作时间分成很多很短的时间片,一个一个时间片的来执行任务,假如多个线程在这些时间片里面轮流执行,但是由于时间片都是很短的,用户根本察觉不到是轮流在执行,在用户看来就是同时在运行多个线程

--------来源自百度

CPU执行代码都是一条一条顺序执行的,但是,即使是单核CPU,也可以同时运行多个任务。因为操作系统执行多任务实际上就是让CPU对多个任务轮流交替执行。

假如我们有语文、数学、英语3门作业要做,每个作业需要30分钟,我们把这3门作业看做3个任务,可以做1分钟语文作业,在做1分钟数学作业,在做一分钟英语作业,这样轮流做下去,在肉眼看来,做作业的速度就非常快,看上去就像同时在做3门作业。

 

/**
* @author 坏小子
* @date 2022/1/19 : 15:16
* @Description :TODO
*/
public class RunableText implements Runnable {

   private String homeWork;

   public RunableText() {
  }

   public RunableText(String homeWork) {
       this.homeWork = homeWork;
  }

   public String getHomeWork() {
       return homeWork;
  }

   public void setHomeWork(String homeWork) {
       this.homeWork = homeWork;
  }

   @Override
   public void run() {
       System.out.println("小明在这一段时间做" + this.homeWork);
  }

   public static void main(String[] args) {
       //这里可以加个循环 可以更好的看出问题
       new Thread(new RunableText("语文")).start();
       new Thread(new RunableText("数学")).start();
       new Thread(new RunableText("英语")).start();
  }
}

结果

小明在这一段时间做语文
小明在这一段时间做英语
小明在这一段时间做数学

多线程的概念

一个任务就是一个进程,浏览器就是一个进程,视频播放器是另一个进程。

子任务就是线程

线程是操作系统调度的最小任务单位,有以下几种类型

  1. 多进程模式

  2. 多线程模式

  3. 多进程+多线程模式

总的来说就是:一个java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main方法,在main方法内部,我们又可以启动多个线程。

使用多线程有以下两种方式

  1. 继承Thread类

        public static void main(String[] args) {
    //       for (int i = 0; i < 10; i++) {
    //           new Thread(new RunableText("语文")).start();
    //           new Thread(new RunableText("数学")).start();
    //           new Thread(new RunableText("英语")).start();
    //       }
           Thread threadText = new ThreadText();
           threadText.start();

      }

        static class ThreadText extends Thread{
           @Override
           public void run(){
               System.out.println("start new thread!");
          }
      }
  2. 创建Thread实例时,传入一个Runnable

       此代码由 【Java 技术驿站】整理
       // 多线程
       ----
       public class Main {
           public static void main(String[] args) {
               Thread t = new Thread(new MyRunnable());
               t.start(); // 启动新线程
          }
      }
       
       class MyRunnable implements Runnable {
           @Override
           public void run() {
               System.out.println("start new thread!");
          }
      }
  3. 线程的优先级

    Thread.setPriority(int n) // 1~10, 默认值5

    优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。

线程的状态

线程的状态有以下几种:

New:新创建的线程,尚未执行;
Runnable:运行中的线程,正在执行run()方法的Java代码;
Blocked:运行中的线程,因为某些操作被阻塞而挂起;
Waiting:运行中的线程,因为某些操作在等待中;
Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
Terminated:线程已终止,因为run()方法执行完毕

当线程启动后,它可以在RunnableBlockedWaitingTimed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。

线程终止的原因有:

线程正常终止:run()方法执行到return语句返回;
线程意外终止:run()方法因为未捕获的异常导致线程终止;
对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。

一个线程还可以等待另一个线程直到其运行结束。例如,main线程在启动t线程后,可以通过t.join()等待t线程结束后再继续运行:

    public static void main(String[] args) {

       System.out.println("start");
       for (int i = 0; i < 10; i++) {
           Thread thread = new Thread(new RunableText("美术"));
           try {
               thread.start();
               thread.join();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       new Thread(new RunableText("语文")).start();
       new Thread(new RunableText("数学")).start();
       new Thread(new RunableText("英语")).start();


       System.out.println("end");

  }

结果

start
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
小明在这一段时间做美术
end
小明在这一段时间做语文
小明在这一段时间做数学
小明在这一段时间做英语

main线程对线程对象t调用join()方法时,主线程将等待变量t表示的线程运行结束,即join就是指等待该线程结束,然后才继续往下执行自身线程。所以,上述代码打印顺序可以肯定是main线程先打印startt线程再打印hellomain线程最后再打印end

如果t线程已经结束,对实例t调用join()会立刻返回。此外,join(long)的重载方法也可以指定一个等待时间,超过等待时间后就不再继续等待。

 

如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。

我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。

中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

public static void main(String[] args) throws InterruptedException{
       Thread t = new MyThread();
       t.start();
       Thread.sleep(1); // 暂停1毫秒
       t.interrupt(); // 中断t线程
       t.join(); // 等待t线程结束
       System.out.println("end");

  }

   static class MyThread extends Thread {
       @Override
       public void run() {
           int n = 0;
           while (! isInterrupted()) {
               n ++;
               System.out.println(n + " hello!");
          }
      }
  }

 

守护线程

 此代码由 【Java 技术驿站】整理
   Thread t = new MyThread();
   t.setDaemon(true);
   t.start();

如果有一个线程没有退出,JVM进程就不会退出,所以保证所有线程都能及时结束

如何创建守护线程呢?方法和普通线程一样,只是在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程 注意 在调用start()方法之前调用setDaemon(true)

public static void main(String[] args) throws InterruptedException {
       Thread t = new MyThread();
       t.setDaemon(true);
       t.start();
       System.out.println("end");
  }

   static class MyThread extends Thread {
       @Override
       public void run() {
           int n = 0;
           while (!isInterrupted()) {
               n++;
               System.out.println(n + " hello!");
          }
      }
  }

 

在来5章

线程同步

当多个线程同时运行时,线程的调度又操作系统决定,程序本身无法决定,因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后再某个时间后继续执行

这个时候,有个单线程模型不存在的问题就来了,如果多个线程同时读写共享变量,会出现数据不一致的问题

 public static void main(String[] args) throws InterruptedException {
       Thread t = new MyThread();
       Thread r = new DecThread();
       r.start();
       t.start();
       r.join();
       t.join();
       System.out.println(RunableText.count);
       System.out.println("end");
  }

   static class MyThread extends Thread {
       @Override
       public void run() {
           for (int i = 0; i < 10000; i++) {
               RunableText.count += 1;
          }
      }
  }

   static class DecThread extends Thread {
       public void run() {
           for (int i = 0; i < 10000; i++) {
               RunableText.count -= 1;
          }
      }
  }

上面的代码很简单,两个线程同时对一个int变量进行操作,一个加10000次,一个减10000次,最后结果应该是0,但是,每次运行,结果实际上都是不一样的

结果

510
end

这是因为对变量进行读写和写入时,必须保证时原子操作。原子操作是指不能被中断的一个或一系列操作

通过加锁和解锁的操作,仅能保证指令总工会是在一个线程执行期间,不会有其他线程会进入此指令区间,即使在执行期线程被操作系统中断执行,其他线程也会因为无法获得锁导致无法进入此指令区间,只有执行线程将锁释放后,其他线程才有机会获得锁并执行,这种加锁和解锁之间的代码快我们称之为临界区,任何时候临界区最多只有一个线程能执行

 public static void main(String[] args) throws InterruptedException {
       Thread t = new MyThread();
       Thread r = new DecThread();
       r.start();
       t.start();
       r.join();
       t.join();
       System.out.println(RunableText.count);
       System.out.println("end");
  }

   static class MyThread extends Thread {
       @Override
       public void run() {
           for (int i = 0; i < 10000; i++) {
               synchronized (RunableText.lock) {
                   RunableText.count += 1;
              }

          }
      }
  }

   static class DecThread extends Thread {

       public void run() {
           for (int i = 0; i < 10000; i++) {
               synchronized (RunableText.lock) {
                   RunableText.count -= 1;
              }

          }
      }


  }

它表示用Counter.lock实例作为锁,两个线程在执行各自的synchronized(Counter.lock) { ... }代码块时,必须先获得锁,才能进入代码块进行。执行结束后,在synchronized语句块结束会自动释放锁。这样一来,对Counter.count变量进行读写就不可能同时进行。上述代码无论运行多少次,最终结果都是0。

同步方法

我们知道java程序依靠synchronized对线程进行同步,使用synchronized的时候,锁住的是哪个对象非常重要。

让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。更好的方法是把synchronized逻辑封装起来。例如,我们编写一个计数器如下:


   public class Counter {
       private int count = 0;
   
       public void add(int n) {
           synchronized(this) {
               count += n;
          }
      }
   
       public void dec(int n) {
           synchronized(this) {
               count -= n;
          }
      }
   
       public int get() {
           return count;
      }
  }

这样一来,线程调用add()dec()方法时,它不必关心同步逻辑,因为synchronized代码块在add()dec()方法内部。并且,我们注意到,synchronized锁住的对象是this,即当前实例,这又使得创建多个Counter实例的时候,它们之间互不影响,可以并发执行

当我们锁住的是this实例时,实际上可以用synchronized修饰这个方法。下面两种写法是等价的:

  1. 1. `    public void add(int n) {`
    2. `        synchronized(this) { // 锁住this`
    3. `            count += n;`
    4. `       } // 解锁`
    5. `   }`
    1. 1. `    public synchronized void add(int n) { // 锁住this`
      2. `        count += n;`
      3. `   } // 解锁`

 

因此,用synchronized修饰的方法就是同步方法,它表示整个方法都必须用this实例加锁。

我们再思考一下,如果对一个静态方法添加synchronized修饰符,它锁住的是哪个对象?

对于static方法,是没有this实例的,因为static方法是针对类而不是实例。但是我们注意到任何一个类都有一个由JVM自动创建的Class实例,因此,对static方法添加synchronized,锁住的是该类的Class实例

可重入锁

public class Counter {
       private int count = 0;
   
       public synchronized void add(int n) {
           if (n < 0) {
               dec(-n);
          } else {
               count += n;
          }
      }
   
       public synchronized void dec(int n) {
           count += n;
      }
  }
   

观察synchronized修饰的add()方法,一旦线程执行到add()方法内部,说明它已经获取了当前实例的this锁。如果传入的n < 0,将在add()方法内部调用dec()方法。由于dec()方法也需要获取this锁,现在问题来了:

对同一个线程,能否在获取到锁以后继续获取同一个锁?

答案是肯定的。JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。

由于Java的线程锁是可重入锁,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁。

 

多线程协调运行的原则就是:当条件不满足时,线程进入等待状态,当条件满足时,线程被唤醒,继续执行任务

/**
* @author 坏小子
* @date 2022/1/19 : 15:16
* @Description :TODO
*/
public class RunableText {

   public static void main(String[] args) throws InterruptedException {

       TaskQueue q = new TaskQueue();
       ArrayList<Thread> ts = new ArrayList<>();
       for (int i = 0; i < 5; i++) {
           Thread t = new Thread() {
               public void run() {
                   // 执行task:
                   while (true) {
                       try {
                           String s = q.getTask();
                           System.out.println("execute task: " + s);
                      } catch (InterruptedException e) {
                           return;
                      }
                  }
              }
          };
           t.start();
           ts.add(t);
      }


       Thread add = new Thread(() -> {
           for (int i = 0; i < 10; i++) {
               // 放入task:
               String s = "t-" + Math.random();
               System.out.println("add task: " + s);
               q.addTask(s);
               try {
                   Thread.sleep(100);
              } catch (InterruptedException e) {
              }
          }
      });
       add.start();
       add.join();
       Thread.sleep(100);
       for (Thread t : ts) {
           t.interrupt();
      }

  }
}

class TaskQueue {
   Queue<String> queue = new LinkedList<>();

   public synchronized void addTask(String s) {
       this.queue.add(s);
       this.notifyAll();
  }

   public synchronized String getTask() throws InterruptedException {
       while (queue.isEmpty()) {
           this.wait();
      }
       return queue.remove();
  }
}

有些仔细的童鞋会指出:即使线程在getTask()内部等待,其他线程如果拿不到this锁,照样无法执行addTask(),肿么办?

这个问题的关键就在于wait()方法的执行机制非常复杂。首先,它不是一个普通的Java方法,而是定义在Object类的一个native方法,也就是由JVM的C代码实现的。其次,必须在synchronized块中才能调用wait()方法,因为wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。

public synchronized String getTask() {
       while (queue.isEmpty()) {
           // 释放this锁:
           this.wait();
           // 重新获取this锁
      }
       return queue.remove();
  }

当一个线程在this.wait()等待时,它就会释放this锁,从而使得其他线程能够在addTask()方法获得this锁。

现在我们面临第二个问题:如何让等待的线程被重新唤醒,然后从wait()方法返回?答案是在相同的锁对象上调用notify()方法。我们修改addTask()如下:

  public synchronized void addTask(String s) {
       this.queue.add(s);
       this.notify(); // 唤醒在this锁等待的线程
  }

注意到在往队列中添加了任务后,线程立刻对this锁对象调用notify()方法,这个方法会唤醒一个正在this锁等待的线程(就是在getTask()中位于this.wait()的线程),从而使得等待线程从this.wait()方法返回。

 

重点关注addTask()方法,内部调用了this.notifyAll()而不是this.notify(),使用notifyAll()将唤醒所有当前正在this锁等待的线程,而notify()只会唤醒其中一个(具体哪个依赖操作系统,有一定的随机性)这是因为可能有多个线程正在getTask()方法内部的wait()中等待,使用notifyAll()将一次性全部唤醒。通常来说,notifyAll()更安全。

多线程是Java实现多任务的基础,Thread对象表示一个线程,我们可以在代码中调用Thread.currentThread()`获取当前线程。

对于多任务,java标准库提供的线程池可以方便的执行这些任务,同时复用线程,web应用程序就是典型的多任务应用,每个用户请求页面我们都会创建一个任务

类似:

 public void process(User user) {
       checkPermission();
       doWork();
       saveStatus();
       sendResponse();
  }

然后通过线性池去执行这些任务

观察process()方法,它内部需要调用若干其他方法,同时,我们遇到一个问题:如何在一个线程内传递状态?

process()方法需要传递的状态就是User实例,有的童鞋会想,简单地传入User就可以了

public void process(User user) {
       checkPermission(user);
       doWork(user);
       saveStatus(user);
       sendResponse(user);
  }

但是往往一个方法又会调用其他很多方法,这样会导致User传递到所有地方

这种在一个线程中,横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。

给每个方法增加一个context参数非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User对象就传不进去了。

Java标准库提供了一个特殊的ThreadLocal,它可以在一个线程中传递同一个对象。

ThreadLocal实例通常总是以静态字段初始化如下

 static ThreadLocal<User> threadLocalUser = new ThreadLocal<>();

使用方式如下

void processUser(user) {
       try {
           threadLocalUser.set(user);
           step1();
           step2();
      } finally {
           threadLocalUser.remove();
      }
  }

通过设置一个User实例关联到ThreadLocal中,在移除之前,所有方法都可以随时获取到该User实例

实际上,可以把ThreadLocal看成一个全局Map:每个线程获取ThreadLocal变量时,总是使用Thread自身作为key:

Object threadLocalValue = threadLocalMap.get(Thread.currentThread());

因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰

最后,特别注意ThreadLocal一定要在finally中清除:

 try {
       threadLocalUser.set(user);
      ...
  } finally {
       threadLocalUser.remove();
  }

这是因为当前线程执行完相关代码后,很可能会被重新放入线程池中,如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的状态带进去

 

 

 

 

posted on 2021-12-27 10:26  紫玉坏小子  阅读(20)  评论(0编辑  收藏  举报