在JDK1.2的时候Java就为多线程提供了ThreadLocal类,JDK5Java引入泛型后,就为ThreadLocal增加了泛型支持,ThreadLocal,通过使用ThreadLocal类可以简化多线程编程时的并发访问,可以很简洁的隔离多线程程序的竞争资源
ThreadLocal其实就是线程局部变量的意思,它为每一个使用该变量的线程提供一个变量值副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突
class Account
{
/* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量
每个线程都会保留该变量的一个副本 */
private ThreadLocal<String> name = new ThreadLocal<>();
// 定义一个初始化name成员变量的构造器
public Account(String str)
{
this.name.set(str);
// 下面代码用于访问当前线程的name副本的值
System.out.println("---" + this.name.get());
}
// name的setter和getter方法
public String getName()
{
return name.get();
}
public void setName(String str)
{
this.name.set(str);
}
}
class MyTest extends Thread
{
// 定义一个Account类型的成员变量
private Account account;
public MyTest(Account account, String name)
{
super(name);
this.account = account;
}
public void run()
{
// 循环10次
for (var i = 0; i < 10; i++)
{
// 当i == 6时输出将账户名替换成当前线程名
if (i == 6)
{
account.setName(getName());
}
// 输出同一个账户的账户名和循环变量
System.out.println(account.getName() + " 账户的i值:" + i);
}
}
}
public class ThreadLocalTest
{
public static void main(String[] args)
{
// 启动两条线程,两条线程共享同一个Account
var at = new Account("初始名");
/*
虽然两条线程共享同一个账户,即只有一个账户名
但由于账户名是ThreadLocal类型的,所以每条线程
都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条
线程访问同一个账户时看到不同的账户名。
*/
new MyTest(at, "线程甲").start();
new MyTest(at, "线程乙").start ();
}
}
ThreadLocal和其他所有的同步机制一样,都是为了解决多线程中队同一个变量的访问冲突,如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,则可以使用ThreadLocal
Java集合中ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的,也就是说多个并发线程向这些集合中存取元素时,就可能会破坏这些集合的数据完整性
如果程序中多个线程可能反问这些集合,就可以使用Collections提供的类方法把这些集合包装成线程安全的集合,Collections提供了如下静态方法:
//使用Collections的synchronizedMap方法讲一个普通的HashMap包装成线程安全的类
HashMap m = Collections.synchronizedMap(new HashMap());
Java5开始,在java.util.concurrent包下提供了大量支持高并发访问的集合接口和实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oi9VCV7o-1587037733007)(/tfl/captures/2020-04/tapd_personalword_1169702794001000476_base64_1587033919_53.png)]
从图中可以看出线程安全的集合类可以分为如下两类:
Java8扩展了ConcurrentHashMap的功能,大致可分为三大类:
使用java.util包下的Collection作为集合对象时,如果该集合对象创建迭代器后集合元素发生改变,则会引发ConcurrentModificationException
由于CopyOnWriteArraySet的底层封装了CopyOnWriteArrayList,因此它的实现机制完全类似于CopyOnWriteArrayList集合
对于CopyOnWriteArrayList集合而言,它采用复制底层数组的方式来实现写操作,add()、remove()、set()等方法都是在操作其副本,因此它是线程安全的,读该集合的时候就直接读取集合本身
同时CopyOnWriteArrayList执行写入的时候需要频繁复制数组,性能比较差,但读是读其本身,读操作很快很安全,因此它更适合于读取操作远远大于写入操作的场景中,例如缓存等
发布-订阅框架是基于异步响应流的,它可以非常方便的处理异步线程之间的流数据交换,它不需要使用数据中心来缓存数据,同时还具有较好的性能
发布-订阅框架使用Flow类的4个静态内部接口作为核心:
Flow.Publisher作为发布者,负责发布数据项并注册订阅者,该接口定义了如下方法来注册订阅者:
Flow.Subscriber接口定义了如下方法:
Java9还为Flow.Publisher提供了一个SubmissionPublisher实现类,它可向当前订阅者异步提交非空的数据项,知道它被关闭,每个订阅者都能以相同的顺序接收到新提交的数据项
创建SubmissionPublisher对象时,需要传入一个线程池作为底层支撑,该类也提供了一个无参数的构造器,该构造器使用ForkJoinPool.commonPool()方法来提交发布者,以此实现发布者向订阅者提供数据项的异步特性
import java.util.concurrent.Flow.*;
import java.util.*;
import java.util.concurrent.*;
public class PubSubTest
{
public static void main(String[] args)
{
// 创建一个SubmissionPublisher作为发布者
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
// 创建订阅者
MySubscriber<String> subscriber = new MySubscriber<>();
// 注册订阅者
publisher.subscribe(subscriber);
// 发布几个数据项
System.out.println("开发发布数据...");
List.of("Java", "Kotlin", "Go", "Erlang", "Swift", "Lua")
.forEach(im -> {
// 发布者通过该方法发布数据项
publisher.submit(im);
try
{
Thread.sleep(500);
}
catch (Exception ex){}
});
// 发布结束
publisher.close();
// 发布结束后,为了让发布者线程不会死亡,暂停线程
synchronized ("fkjava")
{
try
{
"fkjava".wait();
}
catch (Exception ex){}
}
}
}
// 创建订阅者
class MySubscriber<T> implements Subscriber<T>
{
// 发布者与订阅者之间的纽带
private Subscription subscription;
@Override // 订阅时触发该方法
public void onSubscribe(Subscription subscription)
{
this.subscription = subscription;
// 开始请求数据
subscription.request(1);
}
@Override // 接收到数据时触发该方法,订阅者通过该方法接收数据
public void onNext(T item)
{
System.out.println("获取到数据: " + item);
// 请求下一条数据
subscription.request(1);
}
@Override // 订阅出错时触发该方法
public void onError(Throwable t)
{
t.printStackTrace();
synchronized ("fkjava")
{
"fkjava".notifyAll();
}
}
@Override // 订阅结束时触发该方法
public void onComplete()
{
System.out.println("订阅结束");
synchronized ("fkjava")
{
"fkjava".notifyAll();
}
}
}