03 Java 并发 线程安全理论基础

线程安全问题
线程安全问题可能是非常复杂的。
竞态条件
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区
public class Counter {
        protected long count = 0;
        public void add(long value){
                this.count = this.count + value;   
        }
}
上例中add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

可见性
可见性体现在:当另一个线程对 共享数据进行修改的时候,另一个线程未必能看到或者未必能马上看到这个数据。


共享的资源才会产生线程安全问题
在并发编程中,经常遇到的情况是多个线程访问共享资源,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。

哪些资源会共享
局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以, 基础类型的局部变量是线程安全的。下面是基础类型的局部变量的一个例子
public void someMethod(){  
  long threadSafeInt = 0;
  threadSafeInt++;
}

局部对象引用和基础类型的局部变量不太一样。尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。下面是一个线程安全的局部引用样例:
public void someMethod(){  
  LocalObject localObject = new LocalObject();
  localObject.callMethod();
  method2(localObject);
}
public void method2(LocalObject localObject){
  localObject.setValue("value");
}
LocalObject 对象没有被方法返回,也没有被传递给 someMethod()方法外的对象。每个执行 someMethod() 的线程都会创建自己的 LocalObject 对象,并赋值给localObject引用。因此,这里的 LocalObject 是线程安全的。事实上,整个 someMethod() 都是线程安全的。即使将 LocalObject 作为参数传给同一个类的其它方法或其它类的方法时,它仍然是线程安全的。当然,如果LocalObject通过某些方法被传给了别的线程,那它就不再是线程安全的了。

对象的成员变量 存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
public class NotThreadSafe{
    StringBuilder builder = new StringBuilder();    
    public add(String text){
        this.builder.append(text);
    }
}
如果两个线程同时调用同一个NotThreadSafe实例上的add()方法,就会有竞态条件问题。

如何避免线程安全问题
要保证线程安全,实际上是要保证2个方面:可见性,原子性。

不可变性final
当多个线程同时访问同一个资源,并且其中的一个或者多个线程对这个资源进行了写操作,才会产生竞态条件。 多个线程同时读同一个资源不会产生竞态条件
我们可以通过创建不可变的共享对象来保证对象在线程间共享时不会被修改,从而实现线程安全。

volatile
可以保证可见性

synchronized
在多个线程访问共享资源时,必须用同步机制。Java 提供多种同步机制。最常用的是  synchronized 关键字

同步机制的建立是基于其内部一个叫 内部锁或者监视锁的实体。每个对象都有一个与之关联的内部锁。通常当一个线程需要排他性的访问一个对象的域时,首先需要请求该对象的内部锁,当访问结束时释放内部锁。在线程获得内部锁到释放内部锁的这段时间里,我们说线程拥有这个内部锁。那么当一个线程拥有一个内部锁时,其他线程将无法获得该内部锁。其他线程如果去尝试获得该内部锁,则会被阻塞。

实例方法同步
实例方法同步是同步在拥有该方法的对象上
public  synchronized void add(int value){
     this.count += value;
}

静态方法同步
静态方法的同步是指同步在该方法所在的类对象上
public  static synchronized void add(int value){
 count += value;
}

同步块
public void add(int value){
     synchronized(this){
       this.count += value;
    }
}
public static void log2(String msg1, String msg2){
        synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);
       }
}


class Account {
    private double balance;

    public  synchronized void addAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(TimeUnit.MILLISECONDS.toMillis(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp+=amount;
        balance = tmp;
    }
    public  synchronized void subAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(TimeUnit.MILLISECONDS.toMillis(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp-=amount;
        balance = tmp;

    }
    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值