Java并发中线程封闭知识点详解

在这篇文章中,我们将探讨线程封闭是什么意思,以及我们如何实现它。 所以,让我们直接开始吧。

线程封闭基础知识点

实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。什么是线程封闭呢?

就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。实现线程封闭有哪些方法呢?

1:ad-hoc线程封闭

这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。也是最糟糕的一种线程封闭。所以我们直接把他忽略掉吧。

2:栈封闭

栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的
局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

3:ThreadLocal封闭

使用ThreadLocal是实现线程封闭的最好方法,有兴趣的朋友可以研究一下ThreadLocal的源码,其实ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。这里就不说ThreadLocal的使用方法了,度娘一下便知。

1. 线程封闭

大多数的并发问题仅发生在我们想要在线程之间共享可变变量或可变状态时。如果在多个线程之间操作共享变量,则所有线程都将能够读取和修改变量的值,从而出现意外或不正确的结果。一种简单的避免此问题的方式是不在线程之间共享数据。 这种技术称为线程封闭,是在我们的应用程序中实现线程安全的最简单方法之一。

Java 语言本身没有任何强制执行线程封闭的方法。线程封闭是通过不允许多个线程同时使用同一个状态的方式的程序设计来实现的,因此由程序实现强制执行。 几种类型的线程封闭,如下所示:

1.1 Ad-Hoc 线程封闭

Ad-hoc 线程封闭描述了线程封闭的方式,由开发人员或从事该项目的开发人员确保仅在单个线程内使用此对象。 这种方式方法可用性不高,在大多数情况下应该避免。

Ad-hoc 线程封闭下的一个特例适用于 volatile 变量。 只要确保 volatile 变量仅从单个线程写入,就可以安全地对共享 volatile 变量执读 - 改 - 写操作。在这种情况下,您将修改限制在单个线程以防止竞争条件,并且 volatile 变量的可见性保证确保其他线程看到最新值。

1.2 栈封闭

栈封闭将变量或对象封闭在线程的栈中。这比 Ad-hoc 线程封闭强得多,因为它通过定义堆栈本身中的变量状态来进一步限制对象的范围。例如,请考虑以下代码:

private long numberOfPeopleNamedJohn(List<Person> people) {
 List<Person> localPeople = new ArrayList<>();
 localPeople.addAll(people);
 return localPeople.stream().filter(person -> person.getFirstName().equals("John")).count();
}

在上面的代码中,我们传递一个 person 对象的 list 但不直接使用它。 相反,我们创建自己的 list,该 list 是当前正在执行的线程的本地 list,并将变量 people中的所有 person 添加到 localPeople。由于我们仅在 numberOfPeopleNamedJohn方法中定义列表,这使得变量localPeople 受到堆栈隔离保护,因为它只存在于一个线程的堆栈上,因此任何其他线程都无法访问它。这使得 localPeople 线程安全。 唯一需要注意的是,不应该让 localPeople 的作用于超过这个方法的范围,以保证堆栈的隔离控制。在定义这个变量时,应该记录或注释为什么要定义这个变量,通常,只有在当前开发人员的脑海中才不让它超出方法的作用域,但是在将来,另一个开发人员可能会不知道为何如此设计而陷入困境。

1.3 ThreadLocal

ThreadLocal允许我们将每个线程 ID 与相应对象的值相关联。 它允许我们为不同的线程存储不同的对象,并维护哪个对象对应于哪个线程。它有 set 和 get 方法,这些方法为使用它的每个线程维护一个单独的 value 副本。get() 方法总是返回从当前正在执行的线程传递给 set()的最新值。 我们来看一个例子:

public class ThreadConfinementUsingThreadLocal {
  public static void main(String[] args) {
    ThreadLocal<String> stringHolder = new ThreadLocal<>();
    Runnable runnable1 = () -> {
      stringHolder.set("Thread in runnable1");
      try {
        Thread.sleep(5000);
        System.out.println(stringHolder.get());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    };
    Runnable runnable2 = () -> {
      stringHolder.set("Thread in runnable2");
      try {
        Thread.sleep(2000);
        stringHolder.set("string in runnable2 changed");
        Thread.sleep(2000);
        System.out.println(stringHolder.get());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    };
    Runnable runnable3 = () -> {
      stringHolder.set("Thread in runnable3");
      try {
        Thread.sleep(5000);
        System.out.println(stringHolder.get());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    };
    Thread thread1 = new Thread(runnable1);
    Thread thread2 = new Thread(runnable2);
    Thread thread3 = new Thread(runnable3);
    thread1.start();
    thread2.start();
    thread3.start();
  }
}

在上面的例子中,我们使用相同的 ThreadLocal 对象 stringHolder 执行了三个线程。正如你在这里看到的,我们首先在 stringHolder 对象的每个线程中设置一个字符串,使其包含三个字符串。然后,经过一些暂停后,我们只更改了第二个线程的值。 以下是该程序的输出:

string in runnable2 changed
Thread in runnable1
Thread in runnable3

正如您在上面的输出中所看到的,线程2的字符串已更改,但线程1和线程3的字符串未受影响。如果我们在从 ThreadLocal 获取特定线程的值之前没有设置任何值,那么它返回null。 线程终止后,“ThreadLocal” 中特定于线程的对象就可以进行垃圾回收了。

(0)

相关推荐

  • Java中的线程同步与ThreadLocal无锁化线程封闭实现

    Synchronized关键字 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(

  • Java并发中线程封闭知识点详解

    在这篇文章中,我们将探讨线程封闭是什么意思,以及我们如何实现它. 所以,让我们直接开始吧. 线程封闭基础知识点 实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发.避免并发最简单的方法就是线程封闭.什么是线程封闭呢? 就是把对象封装到一个线程里,只有这一个线程能看到此对象.那么这个对象就算不是线程安全的也不会出现任何安全问题.实现线程封闭有哪些方法呢? 1:ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现.也是最糟糕的一种线程封闭.所以我们直接把他忽略掉

  • Java并发编程Semaphore计数信号量详解

    Semaphore 是一个计数信号量,它的本质是一个共享锁.信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可:当信号量中有可用的许可时,线程能获取该许可:否则线程必须等待,直到有可用的许可为止. 线程可以通过release()来释放它所持有的信号量许可(用完信号量之后必须释放,不然其他线程可能会无法获取信号量). 简单示例: package me.socketthread; import java.util.concurrent.ExecutorService;

  • Java并发编程总结——慎用CAS详解

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

  • Java多线程中ReentrantLock与Condition详解

    一.ReentrantLock类 1.1什么是reentrantlock java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现.这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外,它还提供了在激烈争用情况下更

  • java集合框架线程同步代码详解

    List接口的大小可变数组的实现.实现了所有可选列表操作,并允许包括null在内的所有元素.除了实现List接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小.(此类大致上等同于Vector类,除了此类是不同步的.)size.isEmpty.get.set.iterator和listIterator操作都以固定时间运行.add操作以分摊的固定时间运行,也就是说,添加n个元素需要O(n)时间.其他所有操作都以线性时间运行(大体上讲).与用于LinkedList实现的常数因子相比,此实现的

  • Java并发编程之Executors类详解

    一.Executors的理解 Executors类属于java.util.concurrent包: 线程池的创建分为两种方式:ThreadPoolExecutor 和 Executors: Executors(静态Executor工厂)用于创建线程池: 工厂和工具方法Executor , ExecutorService , ScheduledExecutorService , ThreadFactory和Callable在此包中定义的类: jdk1.8API中的解释如下: 二.Executors

  • Java并发编程之Volatile变量详解分析

    目录 一.volatile变量的特性 1.1.保证可见性,不保证原子性 1.2.禁止指令重排 二.内存屏障 三.happens-before Volatile关键字是Java提供的一种轻量级的同步机制.Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量, 相比synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度. 但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其

  • Java并发编程预防死锁过程详解

    这篇文章主要介绍了Java并发编程预防死锁过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在java并发编程领域已经有技术大咖总结出了发生死锁的条件,只有四个条件都发生时才会出现死锁: 1.互斥,共享资源X和Y只能被一个线程占用 2.占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X 3.不可抢占,其他线程不能强行抢占线程T1占有的资源 4.循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有

  • java.lang.Runtime.exec() Payload知识点详解

    有时,通过Runtime.getRuntime().exec()执行命令的有效负载有时会失败.使用Web Shell,反序列化利用或通过其他媒介时,可能会发生这种情况. 有时这是因为重定向和管道字符的使用在启动过程的上下文中没有意义.例如,在shell中执行ls> dir_listing会将当前目录的列表输出到名为dir_listing的文件中.但是在exec()函数的上下文中,该命令将被解释为获取>和dir_listing目录的列表. 有时,StringTokenizer类会破坏其中包含空格

  • Java并发编程之Exchanger方法详解

    简介 Exchanger是一个用于线程间数据交换的工具类,它提供一个公共点,在这个公共点,两个线程可以交换彼此的数据. 当一个线程调用exchange方法后将进入等待状态,直到另外一个线程调用exchange方法,双方完成数据交换后继续执行. Exchanger的使用 方法介绍 exchange(V x):阻塞当前线程,直到另外一个线程调用exchange方法或者当前线程被中断. x : 需要交换的对象. exchange(V x, long timeout, TimeUnit unit):阻塞

随机推荐