10. 线程安全

来源:互联网 发布:手机php电视直播源码 编辑:程序博客网 时间:2024/05/16 07:36

Java中共享的数据可以分为5类,分别是不可变、绝对线程安全、相对线程安全、线程兼容、线程对立

1、不可变
Java语言中,不可变的对象一定是线程安全的,无论是对象的方式实现,还是方法的调用者,都不需要采取任何措施。
注意:final声明基本的基本数据类型和对象是有区别的。
2、线程绝对安全
3、相对线程安全
4、线程兼容
5、线程对立

线程安全实现方法

1.互斥同步

互斥同步是一种常见的并发正确性保障手段,同步是指在多个线程并发访问共享数据是,保证共享数据在同一时刻只被一个(或一些)线程使用,而互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。
Java中,最基本的互斥同步是synchronized关键字,这个关键字经过编译之后会形成monitorenter和monitorexit这两个字节码指令,这两个自己码都需要一个reference类型的参数来指明要锁定和解锁的对象,如果java程序中synchroized明确指定了对象参数,那就是这个对象的reference,如果没用明确指定,那就根据synchroized修饰的类方法或实例方法,去取对应的Class对象或实例对象来作为所对象。
根据虚拟机规范要求,执行monitorenter指令时,首先尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有锁,把锁的计数器加1,对应的在执行moniorexit指令时会将锁计数器减1,当计数器为0时,锁就被释放,如果获取锁失败,那么当前线程就要阻塞等待,直到随想锁被另一个线程释放为止。
注意:synchroized同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。其次是同步块在进入的线程执行完之前,会阻塞后面其他线程的进入。
Java的线程是映射到操作系统的原生线程之上的,因此状态转换需要耗费很多的处理器时间,对于简单的同步块来说,状态转换消耗的时间可能比代码执行的时间还要长,所以synchroized是Java语言中一个重量级的操作。虚拟机本省也会进行一些优化,譬如在通知操作系统阻塞线程之前加入一段自旋等待过程,避免频繁的切入到核心态中。
JUC
除了synchroized之外,还可以使用java.util.cuncurrent包中的重入锁来实现同步。它是API层面的互斥锁。
它有以下三个高级功能:
等待可中断、可实现公平锁、锁可以绑定多个条件。
等待可中断是指:当前持有所得线程长期不释放锁的时候,整袋等待的线程可以选择放弃等待,可改为处理其他事情,可中断特性对处理执行时间长的同步块很有帮助。
公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机回获得锁,synchroized中的锁是非公平的,ReentranLock默认情况下也是非公平的,但可通过带布尔值得构造函数要求使用公平锁。
锁绑定多个条件:ReentrantLock对象可以同时绑定多个Conndition对象,而synchroized中,锁对象的wait和notify方法可以实现一个隐含的条件。

2.非阻塞同步

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步,从处理问题的方式上说,互斥同步是属于一种悲观的并发策略,即无论共享数据是否真的会出现竞争,他都要进行加锁。
因此,可以由另外一种选择:基于冲突检测的乐观并发策略,就是说先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果有共享数据争用,产生了冲出,再采取其他的补偿措施。这就是非阻塞同步。
典型代表:CAS

3.非同步方案

一些方法本来就不涉及共享数据,那么他自然就无需任何同步措施去保证正确性。因此一些代码天生就是线程安全的。
可重入代码:也叫做纯代码,它不依赖存储在对上的数据和公共的系统资源。用到的状态量都是由参数传入,不可调用非可重入方法等。
线程本地存储:Java中可以通过java.lang.ThreadLocal类来实现线程本地存储的功能,每一个线程的Thread对象中都有一个ThreadLocalMap
对象。

原创粉丝点击