标签:err search eem util 多线程 his 副本 就是 print
?Java还为线程安全提供了一些工具类,如ThreadLocal类,它代表一个线程局部变量,通过把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本,从而避免并发访问线程安全问题。除此之外,Java5还新增了大量的线程安全类。
?早在JDK1.2推出之时,Java就为多线程编程提供了一个ThreadLocal类;从Java5.0以后,Java引入了泛型支持,Java为该ThreadLocal类增加了泛型支持,即ThreadLocal
ThreadLocal类的用法非常简单,它只提供了如下三个public方法
public class ThreadLocalTest {
public static void main(String[] args) {
Acct acct = new Acct("init_name");
/**
* 虽然两个线程共享同一个账户,即只有一个账户名
* 但是由于账户名是ThreadLocal类型的,所以每个线程都完全拥有各自的账户副本,
* 因此在i==6之后,将看到两个线程访问同一个账户时出现不同的账户名
*/
new MyTest("t_A", acct).start();
new MyTest("t_B", acct).start();
}
}
class MyTest extends Thread {
private Acct acct;
public MyTest(String t_name, Acct acct) {
super(t_name);
this.acct = acct;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 6) {
acct.setName(getName());
}
System.out.println(acct.getName() + " i:" + i);
}
}
}
class Acct {
private ThreadLocal<String> name = new ThreadLocal<>();
public Acct(String name) {
this.name.set(name);
System.out.println("---" + this.name.get());
}
public String getName() {
return this.name.get();
}
public void setName(String name) {
this.name.set(name);
}
}
运行结果:
---init_name
null i:0
null i:0
null i:1
null i:1
null i:2
null i:3
null i:2
null i:4
null i:3
null i:5
null i:4
t_A i:6
null i:5
t_A i:7
t_B i:6
t_A i:8
t_B i:7
t_A i:9
t_B i:8
t_B i:9
?ThreadLocal和其他所有的同步机制一样,都是为了解决多线程中对同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。该变量是多个线程共享的,所以需要使用这种同步机制,需要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等。在这种情况下,系统并没有将者份资源复制多份,只是采用了安全机制来控制对这份资源的访问而已。
?ThreadLocal从另一个角度来解决多线程的并发访问,ThreadLocal将需要并发访问的资源复制多份,每个线程拥有一份资源,每个线程都拥有自己的资源副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把线程不安全的整个变量封装进ThreadLocal,或者把该对象与线程关联的状态使用ThreadLocal。
?ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式;而ThreadLocal是为了隔离多个线程的数据共享,从根本上避免多个线程之间对共享资源(变量)的竞争,也就不需要对多个线程进行同步了。
?Java集合中ArrayList,LinkedList,HashSet,TreeSet,HashMap,TreeMap等都是线程不安全的,也就是说,当多个线程并发向这些集合中存,取元素时,就可能会破坏这些集合数据完整性。
?如果程序中有多个线程可能访问以上这些集合,就可以使用Collections提供的类方法把这些集合包装成线程安全的集合。Collections提供了如下几个静态方法。
?实际上从Java5开始,在java.util.concurrent包下提供了大量支持高并发并访问的集合接口和实现类:
这些线程安全的集合类可分为如下两类
?其中以Concurrent开头的集合代表支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但是读取操作不必锁定。以Concurrent开头的集合类采用了更负载的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
?当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue是一个恰当的选择。ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多线程访问ConcurrentLinkedQueue集合时无须等待。
?在默认情况下,ConcurrentHashMap 支持16个线程并发写入,当有超过16个线程并发向该Map中写入数据时,可能有一些线程需要等待。实际上,程序通过设置concurrentLevel构造参数(默认值为16)来支持更多的并发写入线程。
?与前面介绍的HashMap和普通集合不同的时,因为ConcurrentLinkedQueue和ConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历元素集合时,该迭代器可能不能反映出迭代器之后所做的修改。但程序不会抛出任何异常。
?Java8扩展了ConcurrentHashMap的功能,Java8为该类新增了30多个新方法,这些方法可借助于Stream和Lambda表达式支持执行聚集操作。ConcurrentHashMap新增的方法大致可分如下三类。
?由于CopyOnWriteArraySet 的底层封装了CopyOnWriteArrayList,因此它的实现机制完全类似于CopyOnWriteArrayList集合。
?对于CopyOnWriteArrayList集合,正如它的名字所暗示,它采用复制底层数据的方式来实现写操作。
?当线程对CopyOnWriteArrayList集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对CopyOnWriteArrayList集合执行写入操作时(包括add(),remove(),set()等方法),该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对CopyOnWriteArrayList集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。需要指出的是,由于CopyOnWriteArrayList执行写入操作需要频繁地复制数组,性能比较差,但是由于读操作与写操作不是操作同一个数组,而且读操作也不需要加锁,因此读操作就很快,很安全。由此可见,CopyOnWriteArrayList适合用在读取操作远远大于写操作的场景中,例如缓存等。
文章内容均取自《疯狂Java讲义-李刚》一书中多线程章节。截取重要知识点作为笔记记录,方便自己回顾。
标签:err search eem util 多线程 his 副本 就是 print
原文地址:https://www.cnblogs.com/everyingo/p/12827130.html