java并发编程之线程安全性和对象共享

读书笔记来自《java并发编程实战》。

线程安全性

可重入锁

  • 可重入意味着获取锁的操作的粒度是线程而不是调用。
  • 实现方法是为每个锁关联一个获取计数值和一个所有者进程。当计数器为0,这个锁被认为可以被任何线程池游。当线程请求一个未被池游的锁时,JVM将记下锁的持有者,并且将计数器置为1,如果同一个线程再次获取这个锁,计数器将递增。当线程退出痛不快,计数器递减。当计数器为0,锁被释放。

对象的共享

可见性

volatile关键字

并发需要三点必须满足的特性

  • 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性:多个线程访问一个变量,其他线程能看到变量的改变
  • 有序性:即程序执行的顺序按照代码的先后顺序执行。

volatile满足了可见性和有序性,没有办法满足原子性。实现原理是,每个线程的工作内存中,复制了变量的值(源自cpu缓存,防止每次都去主存获取)。当某个线程更改变量的值,其他线程的缓存失效,必须去主存重新获取。更改完成立马写回主存。

所以使用volatile的场景是能够保证操作原子性的情况变量的更改不依赖于当前值,或者可以确保只有单个线程更新变量的值。

加锁可以保证可见性

加锁不仅局限于互斥行为,还包括了内存可见性。

发布和逸出

this引用逸出

public class ThisEscape{
    public ThisEscape (EventSource source){
        source.registerListener{
            new EventListener(){
                public void onEvent(Event e){
                    doSomething(e);
                }
            }
        };
    }
}   

在对象没有确定完全初始化之前就发布了this引用。在构造函数中注册了监听事件,但是此时ThisEscape对象还没有完全初始化。

安全的对象构造过程

public class SafeListener{
    private final EventListener listener;

    private SafeListener(){
        listener =new EventListener(){
            public void onEvent(Event e){
                doSomething(e);
            }
        };
    }

    public static SafeListener newInstance(EventSource source){
        SafeListener safe=new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

在执行source.registerListener(safe.listener);之前, SafeListener已经完全初始化。

线程封闭

Ad-hoc线程封闭

完全由程序控制维护线程的封闭性,十分脆弱,尽量要少用。

栈封闭

即使用的变量均为局部变量,每次调用方法都会把局部变量压栈,同样也不存在线程安全的问题。

但是注意不可以对传入的引用做修改(函数副作用)或者发布方法中的一个引用(如集合引用),这样会导致逸出,带来线程安全问题。且不易维护。

Threadlocal类

在每个线程中都有一份变量的备份,实现原理是利用map(ThreadlocalMap, key是threadlocal)。解决了对于每个线程都使用的变量需要同步的问题,比如创建数据库的连接,session管理。

实现应用程序框架时,使用了大量的Threadlocal。如EJB调用,J2EE需要将一个事务上下文与某个执行中的线程关联起来。通过将事物上下文保存在静态的Threadlocal中实现。当框架需要知道当前运行的是哪一个事务的时候,只要从Threadlocal中读取上下文。

这种方法会降低代码的可重用性,带来隐含的耦合。使用时需要小心。

不变性

不可变对象一定是线程安全的。

如何构造一个不变的类。

  • final修饰变量和类
  • 不提供setter方法
  • 构造方法里初始化所有的值
  • getter方法返回值的clone
  • 对象的clone采用深度复制
  • 类成员变量被修饰成private

安全发布

安全地发布一个对象,对象的应用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用
  • 将对象的应用保存到volatile类型的域或者AtomicReferance对象中
  • 将对象的引用保存到某个正确构造对象的final类型域中
  • 将对象的引用保存到一个由锁保护的域中

利用容器

线程安全库中的容器类提供了以下的安全发布保证:

  • 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
  • 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程
  • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。
  • 类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。

利用静态变量

静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布

猜你喜欢

转载自blog.csdn.net/C_J33/article/details/79815343