当多个线程去访问某个类时,若是类会表现出咱们预期出现的行为,那么能够称这个类是线程安全的。html
操做并不是原子。多个线程执行某段代码,若是这段代码产生的结果受不一样线程之间的执行时序影响,而产生非预期的结果,即发生了竞态条件,就会出现线程不安全;java
常见场景:安全
count++
。它自己包含三个操做,读取、修改、写入,多线程时,因为线程执行的时序不一样,有可能致使两个线程执行后count只加了1,而原有的目标确实但愿每次执行都加1;- 单例。多个线程可能同时执行到
instance == null
成立,而后新建了两个对象,而原有目标是但愿这个对象永远只有一个;public MyObj getInstance(){ if (instance == null){ instance = new MyObj(); } return instance } 复制代码
解决方式是:当前线程在操做这段代码时,其它线程不能对进行操做bash
常见方案:多线程
- 单个状态使用 java.util.concurrent.atomic包中的一些原子变量类,注意若是是多个状态就算每一个操做是原子的,复合使用的时候并非原子的;
- 加锁。好比使用 synchronized 包围对应代码块,保证多线程之间是互斥的,注意应尽量的只包含在须要做为原子处理的代码块上;
synchronized的可重入性
当线程要去获取它本身已经持有的锁是会成功的,这样的锁是可重入的,synchronized是可重入的oracle
class Paxi { public synchronized void sayHello(){ System.out.println("hello"); } } class MyClass extends Paxi{ public synchronized void dosomething(){ System.out.println("do thing .."); super.sayHello(); System.out.println("over"); } } 复制代码
它的输出为dom
do thing .. hello over 复制代码
修改不可见。读线程没法感知到其它线程写入的值ui
常见场景:this
- 重排序。在没有同步的状况下,编译器、处理器以及运行时等都有可能对操做的执行顺序进行调整,即写的代码顺序和真正的执行顺序不同,致使读到的是一个失效的值
- 读取long、double等类型的变量。JVM容许将一个64位的操做分解成两个32位的操做,读写在不一样的线程中时,可能读到错误的高低位组合
常见方案:atom
- 加锁。全部线程都能看到共享变量的最新值;
- 使用Volatile关键字声明变量。只要对这个变量产生了写操做,那么全部的读操做都会看到这个修改;
注意:Volatile并不能保证操做的原子性,好比
count++
操做一样有风险,它仅保证读取时返回最新的值。使用的好处在于访问Volatile变量并不会执行加锁操做,也就不会阻塞线程。
读取-修改-写入
使用final去修饰字段,这样这个字段的“值”是不可改变的
注意final若是修饰的是一个对象引用,好比set,它自己包含的值是可变的
建立一个不可变的类,来包含多个可变的数据。
class OneValue{
//建立不可变对象,建立以后没法修改,事实上这里也没有提供修改的方法
private final BigInteger last;
private final BigInteger[] lastfactor;
public OneValue(BigInteger i,BigInteger[] lastfactor){
this.last=i;
this.lastfactor=Arrays.copy(lastfactor,lastfactor.length);
}
public BigInteger[] getF(BigInteger i){
if(last==null || !last.equals(i)){
return null;
}else{
return Arrays.copy(lastfactor,lastfactor.length)
}
}
}
class MyService {
//volatile使得cache一经更改,就能被全部线程感知到
private volatile OneValue cache=new OneValue(null,null);
public void handle(BigInteger i){
BigInteger[] lastfactor=cache.getF(i);
if(lastfactor==null){
lastfactor=factor(i);
//每次都封装最新的值
cache=new OneValue(i,lastfactor)
}
nextHandle(lastfactor)
}
}
复制代码
实例封闭。将一个对象封装到另外一个对象中,这样可以访问被封装对象的全部代码路径都是已知的,经过合适的加锁策略能够确保被封装对象的访问是线程安全的。
java中的Collections.synchronizedList使用的原理就是这样。部分代码为
public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } 复制代码
SynchronizedList的实现,注意此处用到的mutex是内置锁
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}
复制代码
mutex的实现
static class SynchronizedCollection<E> implements Collection<E>, >Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
if (c==null)
throw new NullPointerException();
this.c = c;
mutex = this; // mutex实际上就是对象自己
}
复制代码
java的监视器模式,将对象全部可变状态都封装起来,并由对象本身的内置锁来保护,便是一种实例封闭。好比HashTable就是运用的监视器模式。它的get操做就是用的synchronized,内置锁,来实现的线程安全
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
复制代码
内置锁
每一个对象都有内置锁。内置锁也称为监视器锁。或者能够简称为监视器
线程执行一个对象的用synchronized修饰的方法时,会自动的获取这个对象的内置锁,方法返回时自动释放内置锁,执行过程当中就算抛出异常也会自动释放。
如下两种写法等效:
synchronized void myMethdo(){
//do something
}
void myMethdo(){
synchronized(this){
//do somthding
}
}
复制代码
私有锁
public class PrivateLock{
private Object mylock = new Object(); //私有锁
void myMethod(){
synchronized(mylock){
//do something
}
}
}
复制代码
它也能够用来保护对象,相对内置锁,优点在于私有锁能够有多个,同时可让客户端代码显示的获取私有锁
类锁
在staic方法上修饰的,一个类的全部对象共用一把锁
把线程安全性委托给线程安全的类
视状况而定。
只有单个组件,且它是线程安全的。
public class DVT{
private final ConcurrentMap<String,Point> locations;
private final Map<String,Point> unmodifiableMap;
public DVT(Map<String,Point> points){
locations=new ConcurrentHashMap<String,Point>(points);
unmodifiableMap=Collections.unmodifiableMap(locations);
}
public Map<String,Point> getLocations(){
return unmodifiableMap;
}
public Point getLocation(String id){
return locations.get(id);
}
public void setLocation(String id,int x,int y){
if(locations.replace(id,new Point(x,y))==null){
throw new IllegalArgumentException("invalid "+id);
}
}
}
public class Point{
public final int x,y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
}
复制代码
线程安全性分析
综上,DVT的安全交给了‘locations’,它自己是线程安全的,DVT自己虽没有任何显示的同步,也是线程安全。这种状况下,就是DVT的线程安全实际是委托给了‘locations’,整个DVT表现出了线程安全。
线程安全性委托给了多个状态变量
只要多个状态变量之间彼此独立,组合的类并不会在其包含的多个状态变量上增长不变性。依赖的增长则没法保证线程安全
public class NumberRange{
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i){
//先检查后执行,存在隐患
if (i>upper.get(i)){
throw new IllegalArgumentException('can not ..');
}
lower.set(i);
}
public void setUpper(int i){
//先检查后执行,存在隐患
if(i<lower.get(i)){
throw new IllegalArgumentException('can not ..');
}
upper.set(i);
}
}
复制代码
setLower和setUpper都是‘先检查后执行’的操做,可是没有足够的加锁机制保证操做的原子性。假设原始范围是(0,10),一个线程调用 setLower(5),一个设置setUpper(4)错误的执行时序将可能致使结果为(5,4)
假设须要扩展的功能为 ‘没有就添加’。
public class ListHelper<E>{
public List<E> list=Collections.synchronizedList(new ArrayList<E>());
...
public synchronized boolean putIfAbsent(E x){
boolean absent = !list.contains(x);
if(absent){
list.add(x);
}
return absent;
}
}
复制代码
这里的putIfAbsent并不能带来线程安全,缘由是list的内置锁并非ListHelper,也就是putIfAbsent相对list的其它方法并非原子的。Collections.synchronizedList是锁在list自己的,正确方式为public boolean putIfAbsent(E x){
synchronized(list){
boolean absent = !list.contains(x);
if(absent){
list.add(x);
}
return absent;
}
}
复制代码
另外能够无论要操做的类是不是线程安全,对类统一添加一层额外的锁。 实现参考Collections.synchronizedList方法