谈谈synchronized
为什么要用synchronized关键字:
synchronized是java的一种内部锁,是一种排他锁,通常也被称为悲观锁,它能够保障原子性,可见性,有序性。
当多个线程去调用同一个方法的时候,如果不用加synchronized锁,就可能出现线程不安全的问题。举个经典的例子,比如两夫妻一个用银行卡,一个用网银同时取同一个账户的钱,
取钱这个操作在银行的后台肯定是一个方法,如果两方同时调用,很有可能造成取了两份的钱,这样肯定是不行的。
synchronized的两种使用方式:
1,synchronized加在方法上
public class T { private int count = 10; public synchronized void m() { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } }
2,synchronized代码块
public class T { private int count = 10; public void m() { synchronized(this) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }
这里两种方式达到的效果是一样的,我们需要关注以下几点:
1,synchronized锁,锁的是什么东西。其实是锁的一个对象,任意对象都可以(String常量,Integer,Long不能使用)。当一个线程拿到锁之后,其他的线程就只能等待当前线程执行完,
并释放锁之后,才能拿到锁并执行。以此来保证线程的安全。
2,这两种方式我们该用哪一种呢?实际开发中,我们应该用代码块的方式,为什么要加锁,通常都是需要访问共享变量才会加锁,一个方法中并不是所有代码都需要访问共享变量,
其他的业务逻辑是不需要加锁的,所以代码块的方式可以提高程序的性能。
3,synchronized是一种可重入的锁,什么意思呢,就是如果synchronized代码块中又调用了另外一个加锁的方法,本来如果锁没有释放,是不能拿到锁的。但是可重入锁是可以的,系统会自动识别。
synchronized的底层实现:
jdk早期的时候,synchronized的底层实现是重量级的,重量到可能需要到操作系统去申请锁的地步,所以造成synchronized的效率非常低。jdk1.6之后进行了改进,有了锁升级的概念。
当我们访问synchronized的时候,HotSpot的实现是这样的,当第一个线程来访问的时候,先在锁对象的头上markword记录这个线程,实际上只要一个线程来访问的时候,是不会加锁的,
只是记录这个线程ID,此时称之为偏向锁。
偏向锁如果有线程竞争的话,比如我第一个线程还没有释放锁,第二个线程又来了,就会自动升级为自旋锁,自旋锁的实现原理就是,线程会一直转圈等待获取锁,如果转圈十次之后,还没有获取到锁
就自动升级为重量级锁。
所以说从效率方面来讲,CAS(后续文章会讲解)并不是一定就比synchronized锁的效率高,理解synchronized的底层实现,我们就可以得到如下结论:
- 被锁住的代码,执行实际短,线程数量少的情况,用CAS。
- 被锁住的代码,执行时间长,线程数量多的情况,用系统锁(synchronized内部锁和lock显示锁)。
为什么这样说呢,假如我有1000个线程,用CAS自旋,那岂不是有999个线程会一直在旋转等待,这样是非常消耗资源的。