二、如何解决线程安全问题?
3、无需同步方案
(1)、线程本地存储(ThrealLocal)
(a)、ThreadLocal是什么?
ThreadLocal,很多地方叫做线程本地变量(个人认为也许把它命名为ThreadLocalVariable更容易让人理解一些,哈哈哈~),也有些地方叫做线程本地存储。
如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,每个线程操作的是自己本地内存中的这个副本变量,而不是共享变量,从而规避了线程安全问题。
ThreaddLocal是什么
(b)、使用ThreadLocal的例子
public class MyRunnable implements Runnable {
private ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public void run(){
try {
threadLocal.set((int) (Math.random() * 100));
Thread.sleep(2000);
System.out.println("线程名:" + Thread.currentThread().getName() + ",变量值:" + threadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
程序运行结果:
线程名:Thread-1,变量值:66
线程名:Thread-0,变量值:27
说明:上面的例子创建了一个MyRunnable实例,并将该实例作为参数传递给两个线程。两个线程分别执行run()方法,并且都在ThreadLocal实例上保存了不同的值。
如果它们访问的不是ThreadLocal对象并且调用的set()方法被同步了,则第二个线程会覆盖掉第一个线程设置的值。但是,由于它们访问的是一个ThreadLocal对象,因此这两个线程都无法看到对方保存的值。也就是说,它们存取的是两个不同的值。
(c)、ThreadLocal内部的数据结构
java.lang.Thread类中有一个变量threadLocals,threadLocals变量的类型是java.lang.ThreadLocal中的一个内部类ThreadLocalMap。这样每一个线程都有一个自己的ThreadLocalMap,ThreadLocalMap中的每个元素是一个Entry,Entry的key是ThreadLocal引用,value是set的值,也就是共享变量在当前线程中的副本。
ThreadLocal原理
// java.lang.Thread的源码
public class Thread implements Runnable {
// ......
/* ThreadLocal values pertaining to this thread.
* This map is maintained by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
// ......
// java.lang.ThreadLocal的源码
public class ThreadLocal<T> {
// ......
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
// ......
}
(2)、使用线程安全的类(比如ConcurrentHashMap)
JDK中有很多已经帮我们实现好的线程安全的类,我们直接拿来用就可以。
同步容器(已过时,现在很少用到)
Vector、HashTable、Collections.synchroniedXXX()
并发容器(JUC)
ConcurrentHashMap:线程安全的HashMap容器,ConcurrentHashMap是如何实现线程安全的,见后续《HashMap和ConcurrentHashMap》的文章介绍。
ConcurrentLinkedQueue:线程安全的队列
5、使用volatile保证变量满足可见性和顺序性,使用synchronized保证原子性,volatile和synchronized搭配使用,保证线程安全
在之前的文章《深入理解volatile关键字》这篇文章中,我们讲到当对某个变量的操作满足原子性、可见性、顺序性这三个特性,对这个变量的操作就是安全的。而volatile只能保证变量满足可见性和顺序性,所以我们让其和synchronized配合使用,来保证线程安全。




