Java 并发设计模式(一)

简介: Thread Local Storage 表示线程本地存储模式。大多数并发问题都是由于变量的共享导致的,多个线程同时读写同一变量便会出现原子性,可见性等问题。局部变量是线程安全的,本质上也是由于各个线程各自拥有自己的变量,避免了变量的共享。


一、Thread Local Storage 模式



1. ThreadLocal 的使用

Thread Local Storage 表示线程本地存储模式。

大多数并发问题都是由于变量的共享导致的,多个线程同时读写同一变量便会出现原子性,可见性等问题。局部变量是线程安全的,本质上也是由于各个线程各自拥有自己的变量,避免了变量的共享。

Java 中使用了 ThreadLocal 来实现避免变量共享的方案。ThreadLocal 保证在线程访问变量时,会创建一个这个变量的副本,这样每个线程都有自己的变量值,没有共享,从而避免了线程不安全的问题。

下面是 ThreadLocal 的一个简单使用示例:

public class ThreadLocalTest {
    private static final ThreadLocal<SimpleDateFormat> threadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    public static SimpleDateFormat safeDateFormat() {
        return threadLocal.get();
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<SimpleDateFormat> task1 = new FutureTask<>(ThreadLocalTest::safeDateFormat);
        FutureTask<SimpleDateFormat> task2 = new FutureTask<>(ThreadLocalTest::safeDateFormat);
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        t1.start();
        t2.start();
        System.out.println(task1.get() == task2.get());//返回false,表示两个对象不相等
    }
}

程序中构造了一个线程安全的 SimpleDateFormat ,两个线程取到的是不同的示例对象,这样就保证了线程安全。


2. ThreadLocal 原理浅析

线程 Thread 类内部有两个 ThreadLocalMap 类型的变量:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

其中第二个变量的用途是创建可继承父线程变量的子线程,只不过这并不常用,主要介绍第一个。

ThreadLocalMap 是一个用于存储 ThreadLocal 的特殊 HashMap,map 中 key 就是 ThreadLocal,value 是线程变量值。只不过这个 map 并不被 ThreadLocal 持有,而是被 Thread 持有。

当调用 ThreadLocal 类中的 set 方法时,就会创建 Thread 中的 threadLocals 属性。

//ThreadLocal的set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//获取Thread中的ThreadLocalMap
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

可以看到,最终的 ThreadLocal 对象和变量值并不是创建在 ThreadLocal 内部,而是 Thread 中的 ThreadLocalMap,ThreadLocal 在这里只是充当了代理的作用。


3. ThreadLocal 内存泄漏问题

存储数据的 TheadLocalMap 被 Thread 持有,而不是 ThreadLocal,主要的原因便是 ThreadLocal 的生命周期比 Thread 要长,如果 ThreadLocal 对象一直存在,那么 map 中的线程就不能被回收,容易导致内存泄漏。

而 Thread 持有 ThreadLocalMap,并且 ThreadLocalMap 对 ThreadLocal 的引用还是弱引用,这样当线程被回收时,map 也能够被回收,更加安全。

但是 Java 的这种设计并没有完全避免内存泄漏问题。如果线程池中的线程存活时间过长,那么其持有的 ThreadLocalMap 一直不会被释放。ThreadLocalMap 中的 Entry 对其 value 是强引用的(对 ThreadLocal 是弱引用),这样就算 ThreadLocalMap 的生命周期结束了,但是 value 值并没有被回收。

解决的办法便是手动释放 ThreadLocalMap 中对 value 的强引用,可以使用 TheadLocal 的 remove 方法。在 finally 语句块中执行。例如下面这个简单的示例:

public class ThreadLocalTest {
    private final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public void test(){
        //设置变量值
        threadLocal.set(10);
        try {
            System.out.println(threadLocal.get());
        }
        finally {
            //释放
            threadLocal.remove();
        }
    }
}

二、Immutability 模式



1. 不可变的概念

Immutability,即不变模式。可以理解为只要对象一经创建,其状态是不能够被改变的,无法进行写操作。

要实现 Immuatability 模式很简单,将一个类本身及其所有的属性都设为 final ,并且方法都是只读的,需要注意的是,如果类的属性也是引用类型,那么其对应的类也要满足不可变的特性。final 应该都很熟悉了,用它来修饰类和方法,分别表示类不可继承、属性不可改变。

Java 中具备不可变性的类型包括:

  • String
  • final 修饰的基本数据类型
  • Integer、Long、Double 等基本数据类型的包装类
  • Collections 中的不可变集合

具备不可变性的类,如果需要有类似修改这样的功能,那么它不会像普通的对象一样改变自己的属性,而是创建新的对象。

下面是 String 的字符串连接方法 concat() 的源码,仔细观察,可以看到最后方法返回的时候,创建了一个新的 Sring 对象:

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    //创建新的对象
    return new String(buf, true);
}

而 Collections 工具可以将集合变为不可变的,完全禁止写、修改等操作。示例如下:

//Collections 中构建不可变集合的方法

Collections.unmodifiableList();

Collections.unmodifiableSet();

Collections.unmodifiableMap();

Collections.unmodifiableSortedSet();

Collections.unmodifiableSortedMap();

---

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

//构建不可变集合

List<Integer> unmodifiableList = Collections.unmodifiableList(list);


unmodifiableList.remove(1);//抛出异常


2. 对象池

对于一个不可变性的类,如果频繁的对其进行修改操作,那么一直会创建性新的对象,这样就比较浪费内存空间了,一种解决办法便是利用对象池。

原理也很简单,新建对象的时候,去对象池看是否存在对象,如果存在则直接利用,如果不存在才会创建新的对象,创建之后再将对象放到对象池中。

以长整型的包装类 Long 为例,它缓存了 -128 到 127 的数据,如果创建的是这个区间的对象,那么会直接使用缓存中的对象。例如 Long 中的 valueOf 方法就用到了这个缓存,然后直接返回:

public static Long valueOf(long l) {
    final int offset = 128;
    //在这个区间则直接使用缓存中的对象
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}



相关文章
|
1天前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
16 4
|
1天前
|
设计模式 缓存 Java
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
Java设计模式:享元模式实现高效对象共享与内存优化(十一)
|
1天前
|
安全 Java
如何在Java中处理并发集合
如何在Java中处理并发集合
|
1天前
|
Java 调度
Java多线程编程与并发控制策略
Java多线程编程与并发控制策略
|
1天前
|
安全 Java 开发者
Java并发编程:理解并发与多线程
在当今软件开发领域,Java作为一种广泛应用的编程语言,其并发编程能力显得尤为重要。本文将深入探讨Java中的并发编程概念,包括多线程基础、线程安全、并发工具类等内容,帮助开发者更好地理解和应用Java中的并发特性。
6 1
|
1天前
|
存储 安全 Java
深入剖析Java并发库:Exchanger的工作原理与应用场景
深入剖析Java并发库:Exchanger的工作原理与应用场景
|
1天前
|
存储 缓存 Java
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
|
18小时前
|
安全 Java 数据安全/隐私保护
解决Java中的并发访问问题
解决Java中的并发访问问题
|
1天前
|
设计模式 监控 安全
设计模式之代理模式(Java)
设计模式之代理模式(Java)
|
1天前
|
设计模式 Java 中间件
深入探索Java设计模式:责任链模式解析与实践
深入探索Java设计模式:责任链模式解析与实践
4 0