线程安全与锁优化

        Brian Goetz对线程安全的定义:当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调度方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的 

        并发处理的广泛应用是使得Amdahl定律替代摩尔定律成为计算机性能发展源动力,是人类压榨计算机运算能力最有力的武器 

线程安全:
          限定为多个线程之间存在共享数据访问。因为如果多个线程之间不存在共享数据的话,那么从线程安全的角度看,程序串行和多线程执行时完全没有区别的。 

java里面安全程度由强到弱排序(也是由Brian Goetz提出的):不可变,绝对线程安全,相对线程安全,线程兼容,线程对立 

不可变:
        可以是基本类型的final;可以是final对象,但对象的行为不会对其状态产生任何影响,比如String的subString就是new一个String对象 。各种Number类型如BigInteger和BigDecimal等大数据类型都是不可变的,但是同为Number子类型的AtomicInteger和AtomicLong则并非不可变 
我觉得原因是它里面状态对象时unsafe对象,所做的操作都是CAS操作,可以保证原子性。 

绝对线程安全:
          他是完全满足Brian Goetz给出的线程安全的定义,一个类要达到这种程度,需要付出很大的,甚至不切实际的代价。 
java里面很多看上去非常安全的类,比如vector其实也不能满足这一点。例子: 
线程A 
for(int i=0; i<vectoe.size();i++) { 
vector.remove(i); 
} 
线程B 
for(int i=0; i<vectoe.size();i++) { 
vector.get(i); 
} 
会报ArrayIndexOutOfBoundsException。 
需要额外的同步 
线程A 
synchronized{ 
for(int i=0; i<vectoe.size();i++) { 
vector.remove(i); 
} 
} 
线程B 
synchronized{ 
for(int i=0; i<vectoe.size();i++) { 
vector.get(i); 
} 
} 
相对线程安全:
       这就是我们通常意义上的线程安全。需要保证对象单独的操作时线程安全的。 比如vector,hashtable,synchronizedCollection包装集合。

 


线程兼容:
        对象本身不是线程安全的,但可以通过同步手段实现。一般我们说的不是线程安全的,绝大多数是指这个。 比如ArrayList,HashMap等 
线程对立:
       不管调用端是否采用了同步的措施,都无法在并发中使用的代码。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。  

线程安全的实现  
1、互斥同步  
在多线程访问的时候,保证同一时间只有一条线程使用。 
临界区(Critical Section),互斥量(Mutex),信号量(Semaphore)都是同步的一种手段 
           java里最基本的互斥同步手段是synchronized,编译之后会形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象(可以通过工具读class文件,这是以后必须要做的),还有个锁的计数器,来记录拥有锁的次数,跟AQS里面的state一样 
        其实在 “Java与线程”里已经提到,java的线程是映射到操作系统的原生线程之上的,不管阻塞还是唤醒都需要操作系统的帮忙完成,都需要从用户态转换到核心态,这是很耗费时间的,是java语言中的一个重量级(Heavyweight)操作,虽然虚拟机本身会做一点优化的操作,比如通知操作系统阻塞之前会加一段自旋等待的过程,避免频繁切换到核心态。
 
ReentrantLock也是一个很好的选择。 
1)ReentrantLock比synchronized增加了一些高级的功能 
2)从性能角度考虑,在JDk1.5时代,多线程环境下synchronized的吞吐量下降得非常严重,而ReentrantLock则能保持在比较稳定的水平线上,但是从1.6开始两者性能上基本持平。所以现在这个理由已经不再是了。
 
        虚拟机未来一定是向原生的synchronized改进,所以提倡在synchronized能实现需求的情况下,优先考虑synchronized

2、非阻塞同步  
        互斥和同步最主要的问题就是阻塞和唤醒所带来的性能问题,所以这通常叫阻塞同步(悲观的并发策略)  
           随着硬件指令集的发展,我们有另外的选择:基于冲突检测的乐观并发策略,通俗讲就是先操作,如果没有其他线程争用共享的数据,操作就成功,如果有,则进行其他的补偿(最常见就是不断的重试),这种乐观的并发策略许多实现都不需要把线程挂起。
我的理解就是把那些 需要同步需要多个操作完成的任务沉到硬件指令来完成  
这类的指令有:  
1)测试并设置(test-and-set)  
2)获取并增加  
3)交换  
4)比较并交换(CAS)  
5)加载链接/条件储存(Load-Linked/Store-Conditional  LL/SC)  
           后面两条是现代处理器新增的处理器指令,在JDK1.5之后,java中才可以使用CAS操作,就是传说中的sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法的包装提供,虚拟机对这些方法做了特殊的处理,及时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用的过程,可以认为是无条件的内联进去。  
            原来需要对i++进行同步,但现在有了这种CAS操作来保证原子性,比如用AtomicInteger。 但是不要忘记了CAS存在一个ABA的问题。可以通过AtomicStampedReference来解决  
3、无同步方案  
      要保证线程安全,并不一定就要进行同步,没因果关系。  
锁优化 
     高效并发是JDK1.6的重要主题(所以我们都会觉得直接跳过1.5用1.6),HotSpot虚拟机开发团队花大量精力实现锁的优化技术 如:适应性自旋、锁消除、锁粗化、轻量级锁、偏向锁等

 

 

 
什么是自旋锁:
          互斥同步最大的问题就是阻塞的实现会影响性能,挂起和恢复线程的操作都需要转入内核态中完成。同时虚拟机的开发团队注意到许多应用上,共享数据的锁状态只会持续很短的时间,为了这段时间去挂起和恢复线程不值得。可以让请求锁的线程稍等一会儿,看看持有锁的线程是否很快释放所。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁

什么是自适应自旋:
           1.6引入了自适应自旋锁,自旋锁的时间不再是固定的,而是由前一次的在同一个锁上的自旋时间及锁的拥有者的状态决定。就是虚拟机对程序锁的状态的预测变得越来越聪明

锁消除:
           指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。判断依据来源于逃逸分析的数据支持。如Stringbuffer的例子,所有方法都是synchronized,但是虚拟机观察对象永远不会“逃逸”到客户端方法外,在及时编译之后,所有同步会被忽略掉。

锁粗化:
          比如Stringbuffer的append("a").append("b").append("c")会将synchronized的范围扩大到整个 。

轻量级锁:
          JDK1.6加入的新型锁机制。具体原理还是无法理解,讲的比较忽悠人。
 
偏向锁:
       JDK1.6引入的一项锁优化。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。JDK6中,偏向锁默认打开,它提高了单线程访问同步资源的性能,但如果你的资源或代码移植处在多线程的环境下,并且对自己的代码非常熟悉,大可禁用偏向锁。-XX:-UseBiasedLocking  
在java编程思想中对synchronized的一点解释: 
1、synchronized关键字的作用域有二种: 
1)
是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用 

2、
除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值