目录java
Java线程安全安全
线程安全严谨定义:当多个线程访问一个对象时,若是不用考虑线程在运行时环境下的调度和交替执行,也不须要进行额外的同步,或者在调用方法进行任何其余的协调操做,调用这个对象的行为均可以得到正确的结果,那么这个对象是线程安全的。多线程
Java语言中的各类操做共享的数据分为如下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立并发
1,不可变ide
不可变对象必定是线程安全的,由于只要一个不可变对象被正确构建出,那么其外部的可见状态就永远不会改变,看不到它在多个线程之中处于不一致的状态。在java中,有两种状况:性能
想要保证一个对象行为不影响本身状态,很简单的能够将对象中带有状态的变量都声明为final。Number中Long、Double、BigInteger等是不可变的,而AtomicInteger和AtomicLong则并不是不可变(至于为何,后续看了源码再来解析)。spa
2,绝对线程安全线程
绝对的线程安全是难以达到的,通常java中的线程安全类都不是以为安全的。好比vector,其操做方法基本都是被synchronized修饰的,虽然方法修饰为同步的,但也不意味着调用它时不须要同步手段了。code
好比分析下列例子orm
1 public class aaa { 2 private static Vector<Integer> vector = new Vector<Integer>(); 3 4 public static void main(String[] args) { 5 while(true){ 6 for(int i=0;i<10;i++){ 7 vector.add(i); 8 } 9 Thread removeThread = new Thread(new Runnable() { 10 @Override 11 public void run() { 12 for(int i=0;i<vector.size();i++){ 13 vector.remove(i); 14 } 15 } 16 }); 17 Thread printThread = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 for(int i=0;i<vector.size();i++){ 21 System.out.println(vector.get(i)); 22 } 23 } 24 }); 25 removeThread.start(); 26 printThread.start(); 27 while(Thread.activeCount()>20); 28 } 29 } 30 }
当一个remove线程和一个get线程一直在对vector作修改的时候,虽然get和remove在vector中确实是线程安全的方法,可是不能保证在调用端依然线程安全。这个例子就表现了这一点,for循环的部分就不是线程安全的,可能当某一时刻remove线程确实对vector执行了remove操做,这也是线程安全的,可是总体从11-16行不是安全的,可能此时执行权由print拿到,他恰好要访问这个刚才被删除了的元素,因而就会产生下面的ArrayIndexOutOfBoundsException异常。
因此说方法虽然是安全的,可是调用端不必定安全,须要加安全措施。
3,线程相对安全
咱们在调用时不须要作额外的保障措施,可是对于一些特定顺序额连续调用,就可能须要在调用端使用额外同步手段保证调用正确性。
4,线程兼容
线程兼容:对象自己并非线程安全的,可是能够经过在调用端正确地使用同步手段来保证对象在并发环境中能够安全地使用。通常说一个类不是线程安全地,就是这种状况。好比与Vector,HashTable对应地ArrayList和HashMap。
5,线程对立
不管调用端是否采起了同步措施,都没法再多线程环境中并发使用的代码。好比suspend()和resume(),两个线程同时持有一个线程对象,一个尝试取中断,另外一个尝试去恢复。
线程安全的实现方法
1,互斥同步
互斥即同一时刻只能有一个线程访问或修改共享变量。互斥实现同步,临界区,互斥量,信号量都是主要的互斥实现方式。
最多见的互斥手段是synchronized关键字,它是通过编译以后会在同步块的先后分别造成monitorenter和monitorexit两个字节码指令。
经过引用对象头中的参数Monitor Address来指明要锁定的和解锁的对象,若未指定,则看其修饰的是实例方法仍是类方法,去对应的对象实例或Class对象来做为锁对象。
执行monitorenter指令时
首先尝试获取对象的锁。若该对象没被锁定,或者当前线程已经拥有了那个对象的锁,锁的计数器加1
执行monitorexit指令时,
会将锁计数器减1。
当计数器为0时,锁就被释放了
若获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
两个注意点:
1,synchronized同步块对同一条线程来讲时可重入的。
2,同步块在已进入的线程执行完以前,会阻塞后面其余线程的进入
ReetrantLock 与sychronized很类似,他们都具有同样的线程重入特性。
区别:
1,前者为API层面的互斥锁,另外一个表现为原生语法互斥锁。
2,reentrantLock有一些高级功能:
i) 等待可中断
指当持有锁的线程长期不释放锁的时候,真该等待的线程能够选择放弃等待,处理其余事情,对处理执行事件很是长的同步块颇有帮助。
ii) 可实现公平锁
iii) 锁能够绑定多个条件
便可以绑定多个Condition对象,而在synchronized中,锁对象的wai()、notify()、或notifyAll()方法能够实现一个隐含的条件,但要和多个条件关联时,就须要额外地添加一个锁。
2,非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒锁带来的性能问题,所以这种同步也称为阻塞同步。
待续。。。