synchronized作用、性质、用法、缺陷详解
synchronized的作用?
官网文档翻译:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误︰如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
通俗点说就是:能够保证同一时刻只有一个线程执行被synchronized保护的代码块,以达到保证并发安全的效果
synchronized的两个用法
对象锁
包括方法锁(默认锁的对象为this当前实例对象)和同步代码块锁(需要自己指定锁的对象)
类锁
指synchronized修饰静态方法,或指定锁为class对象(synchronized(*.class))
类锁的概念:一个类可以有多个实例,但是只能有一个Class对象,所谓类锁锁的就是这个类的Class对象,因此类锁在同一时刻只能被一个对象拥有。多个线程之间用不同的实例获取类锁的仍然存在互斥关系。
多线程访问同步方法的七种情况
1、两个线程同时访问一个对象的同步方法(同步执行)
2、两个线程访问的是两个对象的同步方法(互不影响)
3、两个线程访问的是synchronized的静态方法(同步执行)
4、同时访问同步方法和非同步方法(非同步方法不受影响)
5、访问同一个对象的不同的普通同步方法(会同步执行,因为获取的是同一个对象的锁,同一时间只能属于一个线程)
6、同时访问静态synchronized和非静态synchronized方法(互不影响,因为他们获取的锁不一样,一个时对象锁,一个时Class对象锁)
7、方法抛出异常后,会释放锁
核心思想:
1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);
2.每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);
3.无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)
可能还有其他的情况,但是都离不开这几个核心思想。
synchronized的性质
可重入
1、什么是可重入?
可重入是指同一个线程当外层函数获取到锁时,内层函数可以直接再次获取该锁。
2、可重入性能带来什么好处?
避免死锁,提升封装性(不用频繁的加锁,解锁)
3、可重入性的粒度是线程而非方法
不管内层函数是递归调用还是调用其他同步方法(当前实例对象),都可以直接获取到锁
可重入性的原理:
通过加锁次数计数器来进行实现的
- JVM负责跟踪对象被加锁的次数
- 线程第一次给对象加锁是,计数器会变为1,后面当同一个线程为此对象加锁时,计数器也会随着累加
- 当任务离开时,计数器会减1,直到所有任务执行完毕,计数器变为0,锁被完全释放
不可中断(原子性)
一旦锁被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁,如果别人永远不释放这个锁,那么我只能一直等待下去
synchronized释放锁的时机只有两种:
1)正常执行完毕
2)抛出异常,JVM帮我们自动释放锁
可以保证可见性
1、什么是可见性?
java内存模型将内存分为主内存和工作内存,每个线程都有自己的工作内存,因为线程的工作内存中执行的速度通常而言要比主存要开,这样可以提高执行速度。
每个线程之间是不能直接进行交互的,线程之间只能通过主存来进行通信,那么在高并发情况下,线程可能不能对变量进行及时的同步,导致其他线程拿到了一个过期的值并在此基础上修改,那么肯定会导致数据不一致的问题,这就是可见性问题。
而synchronized代码块的执行过程中,总是从主存中读取数据,并且修改后会立即同步会主存,因此保证了线程之间的可见性,而且它的读写操作是原子性的(不可被中断性质),因此它可以避免出现线程安全问题。
根据happens-before
原则,不仅synchronized中的代码具有可见性,synchronized代码块之前的代码样具有可见性~
synchronized的缺陷
没有人是完美的,synchronized也有自己的缺陷:
- 效率低
体现在锁的释放情况少,试图获取锁时不能设置超时,也不能中断一个试图获取锁的线程。synchronized释放锁的情况只有两种(正常执行完毕、抛出异常)其他情况都不会释放锁,假设持有锁的线程正在执行比较耗时的IO操作,那么其他线程就只能一直等待。
相比之下Lock类,更加灵活,可以拥有中断的能力:
1)中断别人。如果我觉得等的时间太长了,我有权中断现在已经获取到锁的线程的执行
2)中断自己。如果我觉得等得时间已经超过了我的预期不想等了,那么我可以中断自己.
- 不够灵活
加锁和释放锁的情况单一,每个锁仅有单一的条件(某个对象),在某些情况下,即使多个线程同时操作也是没有问题的,比如读操作。那么在这种情况下使用读写锁更加灵活。
- 无法知道是否成功获取到了锁
synchronized的这些缺点会在Lock锁中找到解决的方案。因为如果存在一些特殊的需求,可以使用Lock锁来完成。