暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

线程安全问题(下)

程序媛和她的猫 2021-01-16
281

二、如何解决线程安全问题?

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配合使用,来保证线程安全。


文章转载自程序媛和她的猫,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论