本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
线程封闭
当访问共享数据时,通常是要使用同步。如果要避免使用同步,就是不提供共享数据。如果仅在单线程中访问数据,就不需要同步,这种技术就叫做线程封闭,它是实现线程安全最简单的方式之一。当某个对象封闭在一个线程当中时将自动实现线程安全性,即使被封闭的对象本身它并不是安全的,实现线程主要有三种方式。
1. Ad-hoc线程封闭
Ad-hoc是指维护线程封闭是由程序自己去实现和维护。Ad-hoc非常脆弱,因为它没有一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到特定的线程上。
2. 栈封闭
栈封闭简单理解就是通过局部变量来实现线程封闭,多个线程访问对象的同一个方法,方法内部的局部变量会拷贝到每个线程的线程栈当中,只有当前线程才能访问到,互不干扰。所以局部变量是不被多个线程所共享的。
示例:
public class ThreadTest {
private int num;
public void test(int key){
int flag=0;
for(int i=0;i<key;i++){
flag=flag+1;
num=num+1;
}
System.out.println(Thread.currentThread().getName()+"num"+num);
System.out.println(Thread.currentThread().getName()+"flag"+flag);
}
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
threadTest.test(10);
}
});
thread1.start();
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
threadTest.test(9);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
# 结果
Thread-0num10
Thread-0flag10
Thread-1num19
Thread-1flag9
复制代码
由示例可以看出,在类上有一个全局变量num,方法里有一个局部变量flag,启动两个线程去调用这个方法,发现num的结果是两个线程结果之和,而flag是互不干扰,所以局部变量是不被多个线程所共享的。
3.ThreadLocal
维护线程封闭一种更规范的方法就是使用ThreadLocal。ThreadLocal提供get和set方法,这些方法为每个使用该变量的线程都存有一份独立的副本因此get总是返回的是当前线程在调用set时设置的值。
ThreadLocal一般用于防止对可变的单实例变量或者全局变量进行共享。例如在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动的时候初始化这个对象,从而避免在调用每个方法都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的,通过将JDBC的连接保存到ThreadLocal中,每个线程都会拥有属于自己的连接。
ThreadLocal代码示例:
/** 线程封闭示例 */
public class Demo7 {
/** threadLocal变量,每个线程都有一个副本,互不干扰 */
public static ThreadLocal<String> value = new ThreadLocal<>();
/**
* threadlocal测试
*
* @throws Exception
*/
public void threadLocalTest() throws Exception {
// threadlocal线程封闭示例
value.set("这是主线程设置的123"); // 主线程设置值
String v = value.get();
System.out.println("线程1执行之前,主线程取到的值:" + v);
new Thread(new Runnable() {
@Override
public void run() {
String v = value.get();
System.out.println("线程1取到的值:" + v);
// 设置 threadLocal
value.set("这是线程1设置的456");
v = value.get();
System.out.println("重新设置之后,线程1取到的值:" + v);
System.out.println("线程1执行结束");
}
}).start();
Thread.sleep(5000L); // 等待所有线程执行结束
v = value.get();
System.out.println("线程1执行之后,主线程取到的值:" + v);
}
public static void main(String[] args) throws Exception {
new Demo7().threadLocalTest();
}
}
# 结果
线程1执行之前,主线程取到的值:这是主线程设置的123
线程1取到的值:null
重新设置之后,线程1取到的值:这是线程1设置的456
线程1执行结束
线程1执行之后,主线程取到的值:这是主线程设置的123
复制代码
通过代码示例,可以看到主线程和子线程的执行结果互不干扰。当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用ThreadLocal。至于ThreadLocal如何实现为每个线程提供单独的副本,请看ThreadLocal源码分析 ,欢迎关注我,一起学习,一起进步。
往期精彩回顾
学习的路上永远都需要志同道合的人一起探讨,一起前行,关注我,一起学习吧