Synchronized的作用
Synchronized
官方解释:
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
一句话总结出Synchronized的作用:
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
Synchronized的地位:
synchronized是Java的关键字,被Java语言原生支持
是最基本的互斥同步手段
必学
synchronized的两个用法
对象锁:
包含方法锁(默认锁对象为this当前实力对象),同步代码块锁(自己制定锁对象)
类锁:
指sychronized修饰静态的方法或指锁为Class对象
第一个用法:对象锁
代码块形式:手动指定锁对象
方法锁形式:synchronized修饰普通方法,锁默认对象为this
第二个用法:类锁
概念:java类可能有有很多个对象,但是只有一个class对象
本质:所以所谓的类锁,不过是Class对象的锁而已
用法和效果:类锁只能在同一时刻被一个对象拥有
形式1:synchronized加载static方法上
形式2:synchronized(*.class)代码块
多线程访问同步方法的7种情况
1.两个线程同时访问一个对象的同步方法
串行
2.两个线程访问的是两个对象的同步方法
锁对象不同,互不干扰,并行
3.如果两个线程访问的是Synchronized的静态方法
串行
4.同时访问同步方法与非同步方法
并行
5.访问同一对象的不同的普通同步方法
同一对象锁,串行
6.同时访问静态synchronized和非静态synchronized方法
锁不同,并行
7.方法抛异常后,会释放锁吗
如果一个线程在进入同步方法后抛出了异常,则另一个线程会立刻进入该同步方法
7种情况的总结
1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应1,5种情况)
2.每个实例都对应有自己的一把锁,不同实例之间互不影响,例如,锁对象是*.class以及synchronized修饰的
是static方法的时候,所有对象共用同一把锁(对应第2,3,4,6种情况)
3.无论是方法正常执行完步或者方法抛出异常,都会释放锁(对应第7种情况)
性质
1.可重入
什么是可重入(递归锁)-----例如:Synchronized,ReentranLock
指的是同一线程的外层方法获得获得锁之后,内层方法可以直接在此获取该锁
例如: 买完车,需要摇号上车牌(锁),这样才能开车(执行相应的方法).
如果我有三辆车,但只有一个车牌,显然只能给一辆车上车牌----获取锁后只能执行一个方法
---------------为不可重入性
反之:一个车牌,三辆车都可以开
---------------为可重入性
好处:
避免死锁,提高封装性
粒度(范围):
可重入粒度测试: 证明线程而非调用(用三种情况来说明和pthread的区别)
情况一:证明同一方法是可重入的---递归调用本方法
package cn.cg; /** * 可重入测试----使用递归方式 * 情况1:同一个类中,同一方法可重入 */ public class SynchronizedTest { int i = 0; //主线程可以重入以this为锁对象的method方法 public static void main(String[] args) { SynchronizedTest s1 = new SynchronizedTest(); s1.method(); } // 同步方法 private synchronized void method() { if (i <=3) { i++; System.out.println(i); method(); } } }
测试通过.
情况二:证明可重入不要求是同一个方法
package cn.cg; /** * 可重入测试---- * 情况2:同一个类中,不同方法可重入 */ public class SynchronizedTest2 { //主线程可以重入以this为锁对象的method方法 public static void main(String[] args) { SynchronizedTest2 s1 = new SynchronizedTest2(); s1.method1(); } // 同步方法 private synchronized void method1() { System.out.println("method1"); method2(); } private synchronized void method2() { System.out.println("method2"); } }
测试通过
情况三:证明可重入不要求是同一个类中的
public class Demo1 { public synchronized void method(){ System.out.println("我是Demo1"); } } class Demo1Zi extends Demo1{ public synchronized void method(){ System.out.println("我是Demo1儿子"); super.method(); } public static void main(String[] args) { Demo1Zi zi = new Demo1Zi(); zi.method(); } }
测试通过
2.不可中断
一旦这个锁已经被别人获得了,如果我还想获取,我只能选择等待或者阻塞,直到别的线程释放这个锁.如果别人永远不释放锁,那么我只能永远地等下去
相比之下,Lock类,可以拥有中断的能力,
第一点:如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行,
第二点:如果我绝得我等待的时间太长不想再等了,也可以退出.
加锁和释放锁的原理
现象
每一个类的实例对应一把锁,每次调用被synchronized修饰的方法都必须首先获得该方法所属调用者实例的锁.否则线程阻塞,
方法一旦执行,该线程就独占了该锁知道该方法返回或者抛出异常,才能释放锁.
获取和释放锁的时机:内置锁(监视器锁)
等价代码
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo2 { private Lock lock = new ReentrantLock(); public synchronized void method1(){ System.out.println("我是synchronized锁住的方法"); } public void method2(){ //加锁 lock.lock(); try { System.out.println("我是ReentranLock锁住的方法"); }finally { lock.unlock(); } } public static void main(String[] args) { Demo2 demo2 = new Demo2(); demo2.method1(); demo2.method2(); } }
synchronized的缺点
1.效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得所得线程
2.不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
3.无法知道是否成功获取到锁