同步代码块和同步方法可以确保以原子的方式执行操作,同时另外一个重要的作用是内存可见性,我们不仅希望防止某个线程正在使用对象的状态,而另一个线程在同时修改这个状态,而且还希望确保一个线程修改了对象状态后,其他线程能够看到发生的状态变化。

可见性

通常我们无法确保执行读操作的线程能够适时的看到其他线程写入的值,为了确保多个线程之间对内存的写入操作的可见性必须使用同步机制。

重排序

在没有同步的情况下,编译器,处理器,以及运行时等都可能对操作的执行顺序进行一些意想不到的调整,在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论

枷锁与可见性

java内置锁可以用于确保某个线程以一种可预见的方式来查看另外一个线程的执行结果,当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,在这种情况下可以保证,在锁被释放之前,A看到的变量值在B获得锁后同样可以由B看到。换句话说,当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作结果。如果没有同步,那么就无法实现上述保证

同步的可见性保证
同步的可见性保证

加锁的含义不仅仅局限于互斥行为,还包括内存的可见性。为了确保所有线程都能得到共享变量的最小值,所有执行读操作或者写操作都必须在同一个锁上同步。

Volatile 变量

Java语言提供了一种稍弱的同步机制,即Volatile变量。用来确保将变量的更新操作通知到其他线程。当把变量声明成volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存上的操作一起重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方。因此在读取volatile变量总会返回最新的写入的值。
在访问volatile变量不会执行加锁的操作,因此也不会使执行线程阻塞,因此可以将volatile变量看作是一种比synchronized关键字更轻量级的同步机制(synchronized关键字在内存可见性上的作用比volatile变量更强)
从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。但是并不建议过度依赖volatile变量的可见性,它通常比使用锁更脆弱,也难以理解。仅当volatile变量能够简化代码的实现以及对同步策略的验证时,才该使用它们。
通常volatile变量用作某个操作完成,发生中断或者状态的标志。

ThreadLocal

ThreadLocal是维持线程封闭性的一种更规范的方法。这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法。这些方法为每个使用该变量的线程都存有一份独立的副本。因此get总是返回由当前执行线程在调用set时设置的最新值

安全的共享对象策略

在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:

线程封闭 线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改
只读共享 在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,任何线程都不能修改它,共享的只读变量包括不可变对象,和事实不可变对象。
线程安全共享 线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
保护对象 被保护的对象只能通过持有特定的锁来访问,保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定的锁保护的对象。