线程安全性

来源:互联网 发布:atmega dip封装单片机 编辑:程序博客网 时间:2024/05/16 07:13

竞态条件与锁

当某个计算结果的正确性取决于多个线程执行的顺序时,就会发生竞态条件。常见的竞态类型是先检查后执行。

先检查后执行的一种常见情况是延迟初始化

public class LazyInitRace{    private People people=null;    public People getInstance(){        if(people==null){            people=new People();        }        return people;    }}

这就包含一个竞态条件,在people未被实例化前,当多个线程都访问getInstance()时,就可能会得到不同的结果。如果不在同一瞬间调用这个方法,就不会出现问题。在people被实例化之后,多次同时访问这个方法,也不会出现问题。

要避免竞态问题,就必须在某个线程修改改变量时,通过某种方式防止其它线程使用这个变量,也就是保证修改操作的原子性。

加锁是java内置的确保原子性的机制。

synchronized关键字加锁:

public class TT{    private Integer a=4;    public static synchronized void do33(){    }    public synchronized void do34(){    }    public synchronized void do35(){            synchronized(a){            }    }}以上第一个synchronized是对TT的Class对象加锁,每一个类的字节码文件被加载到内存中以后,就会生成一个Class对象。第二个synchronized是对创建的TT对象加锁,比如TT t=new TT();那么这个锁就对t生效。第三个synchronized是对a对象加锁。

java 的内置锁是一种互斥锁。

java的锁是可以重入的,也就是说,当前线程持有了某个对象的锁,在线程中再次获取该对象的锁,是可以成功的。例子如下:

public class T2{    public synchronized void t1(){        System.out.println("t1执行了");        synchronized(this){            System.out.println("t1中的同步块执行了");        }    }    public static void main(String[] args){        T2 t=new T2();        t.t1();    }}例子2public class Father {    public synchronized void play(){        System.out.println("father play");    }}public class Son extends Father {    public synchronized void play(){        System.out.println("son play");        super.play();    }    public static void main(String[] args){        new Son().play();    }}

一种常见的加锁约定是,把所有的可变状态封装在对象内部。通过内置锁对所有访问可变状态的路径(类似于get,set的方法)进行同步。Vector等集合类采用了这种模式。

不过上面这种方式也是有缺陷的,如果以后新增了一个方法,能够访问和修改哪些可变的变量,却又忘记增加synchronized关键字进行同步,那么就可能出现问题。

线程安全的类所组成的操作很多时候也需要额外的同步,如下:

if(!vector.contains(a))    vector.add(a)

这就存在竞态条件,需要把这2个操作组成一个原子性操作。

原创粉丝点击