编写正确的并发程序的关键在于共享对象、可变的状态进行访问管理

锁与可见性

内置锁可以用来确保一个线程以某种可预见的方式看到另一个线程的影响。
VAoXzd.png
锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的,可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。

1
2
3
4
5
6
7
8
9
10
11
@ThreadSafe
public class SynchronziedInteger{
private int value;
public synchronized int get(){
return value;
}

public synchronized void set(int value){
this.value=value;
}
}
  • 最低限安全性
    当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值。但它至少可以看到某个线程在那里设定的一个真实数值,而不是一个凭空而来的值。这样的保证称为最低限的安全性。

volatile 变量

它可以确保对一个变量的更新以可预见的方式告知其它的线程。当一个域被声明为volatile类型后,编译器运行时会监视这个变量,它是共享的,而且对它的操作不会与其它内存操作一起被重排序。volatile变量不会缓存在寄存器或者缓存在对其它处理器隐藏的地方。所以读一个volatile变量时,总会返回某一线程所写入的最新值。

当一个共享变量声明为volatile后:
1.当写一个volatile变量时,JMM会把该线程对应的本地内存种的变量强制刷新到主内存中去。
2.这个写操作会导致其它线程中的缓存无效。
3.volatile会禁止语义重排序。

如何理解:
线程A向volatile变量写入值,随后线程B读取该变量,所有A执行写操作前可见的变量的值,在B读取了volatile变量后名称为对B也是可见的。所以从内存可见性的角度看,写入volatile变量就像时退出同步块,读取volatile变量就像进入同步块。

volatile变量通常被当作标识完成,中断,状态的标记。

加锁可以保证可见性与原子性;volatile变量只能保证可见性。

使用volatile变量的标准

  • 写入变量时并不依赖变量的当前值;或能够保证只有单一线程修改变量的值。
  • 变量不需要与其它的状态变量共同参与不变的约束。
  • 访问变量时,没有其它的原因需要加锁。

线程封闭

一个可以避免同步的方式是不共享数据,如果数据仅在单线程中被访问,就不需要任何同步。线程封闭技术是实现线程安全的最简单的方式之一。

ThreadLocal

使用ThreadLocal允许将每个线程与持有的数值对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回当前执行线程通过set设置的最新值。
可以使用ThreadLocal保证各个线程之间的变量互相隔离、互相独立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ThreadLocalTest{
static class MyThread extends Thread{
private static ThreadLocal<Integer> threadLocal=new ThreadLocal<>();

@override
public void run(){
super.run();
for(int i=0;i<3;i++){
threadLocal.set(i);
System.out.println(getName()+"threadLocal.get()="+threadLocal.get());

}
}

}

public static void main(String[] args){
MyThread mythreadA=new MyThread();
mythreadA.setName("ThreadA");

MyThread mythreadB=new MyThread();
mythreadB.setName("ThreadB");

mythreadA.start();
mythreadB.start();

}

}
1
2
3
4
5
6
7
8
输出==>
ThreadA threadLocal.get() = 0
ThreadB threadLocal.get() = 0
ThreadA threadLocal.get() = 1
ThreadA threadLocal.get() = 2
ThreadB threadLocal.get() = 1
ThreadB threadLocal.get() = 2

ThreadLocal中常用的方法:
get() set(T value) initialValue() remove()

get():获取与当前线程相关联的ThreadLoal值。如果之前没有被设置过值,则会调用initialValue()方法。
set(T value):设置与当前线程相关联的ThreadLocal的值。
initialValue():设置于当前线程相关的TheadLocal的初始值。
remove():将与当前线程关联的ThreadLocal值删除。

使用私有锁保护状态

1
2
3
4
5
6
7
8
9
10
public class PrivateLock{
private final byte[] lock=new byte[0];
private Widget widget;
void someMethod(){
synchronized(lock){
//访问或修改Widget的状态
}
}
}

使用私有锁对象,而不是对象的内部锁(或其它任何可公共访问的锁),有很多好处。私有的锁对象可以封装锁,这样客户代码无法获取它,然而可公共访问的锁允许客户代码涉足它的同步策略。客户不正确的获得到了另一个对象的锁,会引起活跃度方面的问题。