JAVA线程安全之synchronized

上一篇文章分析了voliate,今天分析一下sychroniazed。与voliate相比,sychronized是重量级同步机制,因为,sychronized需要对共享对象上锁,其他并发线程想要访问该共享对象的话,需要阻塞等待,阻塞和唤醒需要更多系统资源的参与,所以是重量级同步机制。

sychronized虽然是重量级同步机制,但是理解起来却比voliate容易的多:*


  1. sychronized通过内存锁实现同步。
  2. sychronized能确保线程安全性,可以彻底避免线程安全问题,

明确指定的情况下,则sychronized直接锁定该对象。否则,如果sychronized作用在实例方法上,则锁定该对象实例。如果sychronized作用在类方法(静态区域或静态方法等)上,则锁定该类对象。

我们可以说以下代码是线程安全的,在多线程环境下,调用方能够得到正确的返回结果。

public class Account {
        private sychronized int counter=0;
        public void doAddCounter(){
            for(int j=0;j<100;j++){
                counter++;
            }
        }

        public int getCounter(){
            return counter;
    }
}

sychronized的工作原理是,当多线程应用访问sychronized共享变量的时候,首先获取对该变量的锁,如果该变量已经有其他的(非当前线程的)锁存在的情况下,当前线程必须阻塞等待,否则,如果该变量存在当前线程的锁,或者不存在锁的话,则当前线程获得执行权,同时锁定该共享变量。

所以,当前线程可以多次获得该共享变量的锁,多次上锁必须多次释放锁。

JDK6之后,JAVA对sychronized的锁机制进行了向“轻量级锁”方向的改善,增加了自旋锁、锁消除、锁粗化、偏向锁等完善机制。

所有的这些改造,都是本着一个原则:能不上锁就不上锁,因为上锁和锁释放意味着线程切换,线程切换操作需要耗费系统资源。

JDK对sychronized锁机制的改造包括:

  1. 自旋锁:基于绝大部分应用获得锁之后的应用处理都会在短时间内完成这一假设,让请求线程在获得锁之前先自旋一会儿---cpu空转,啥也不干只是等待锁释放。
  2. 锁消除:代码要求加锁,但是虚拟机判断不需要加锁的情况,就不加锁。
  3. 锁粗化:比如一段代码在循环内加锁,这种情况会导致频繁加锁,虚拟机会适当扩大锁范围,变为一个锁,避免频繁加锁。
  4. 偏向锁:锁偏向于第一个获取他的线程,如果在接下来的执行过程中,该锁一直没有被其他线程获取,则持有偏向锁的线程将永远不需要再同步。

偏向锁实际上只是在对象头设置了一个标志并记录了获取该偏向锁的线程ID。但是记录线程ID占用了对象头中记录该对象HashCode的位置,所以,请求过HashCode的对象无法使用偏向锁。

总结:使用synchronized可以彻底解决线程安全问题,而且是重量级的同步机制,非并发环境下没必要使用,在必须使用synchronized确保线程安全的情况下,需要确认锁对象及锁范围,避免资源浪费导致系统性能问题。

你可能感兴趣的:(java)