Synchronzied的两个用法:
对象锁
包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
-
代码块形式:手动指定锁对象
@Override public void run() { synchronized (this) { System.out.println("我是对象锁的代码块形式。我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
当业务度比较复杂的时候需要手动创建对象锁,如下
public class SynchronizedObjectCodeBlock2 implements Runnable{ static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2(); Object lock1 = new Object(); Object lock2 = new Object(); @Override public void run() { synchronized (lock1) { System.out.println("我是对象锁lock1的代码块形式。我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " lock1部分运行结束。"); } synchronized (lock2) { System.out.println("我是对象锁lock2的代码块形式。我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " lock2部分运行结束。"); } } public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
}
```
-
方法锁形式:synchronized修饰普通方法,锁对象默认为this
/** * 对象锁,方法锁形式 */ public class SynchronizedObjectMethod3 implements Runnable{ static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3(); @Override public void run() { method(); } public static void main(String[] args) { Thread th1 = new Thread(instance); Thread th2 = new Thread(instance); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } public synchronized void method() { System.out.println("我的对象锁的方法修饰符形式,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
类锁
指synchronized修饰静态的方法或指定锁为Class对象,Java类可能有很多个对象,但是只有一个Class对象;本质上所谓的类锁,不过是Class对象的锁而已。类锁只能在同一时刻被一个对象拥有
-
静态方法锁,Synchronized加在static方法上
/** * 类锁的第一种形式,static形式 */ public class SynchronizedClassStatic4 implements Runnable{ static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4(); static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4(); @Override public void run() { method(); } public static void main(String[] args) { Thread th1 = new Thread(instance1); Thread th2 = new Thread(instance2); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } public static synchronized void method() { System.out.println("我是类锁的第一种形式:static,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
-
Synchronized(*.class)代码块
/** * 类锁的第二种形式,class */ public class SynchronizedClassClass5 implements Runnable{ static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5(); static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5(); @Override public void run() { method(); } public static void main(String[] args) { Thread th1 = new Thread(instance1); Thread th2 = new Thread(instance2); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } public void method() { synchronized (SynchronizedClassClass5.class){ System.out.println("我是类锁的第二种形式:synchronized(*.class),我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } } }
多线程调试—看线程生命周期
右键断点就可以查看到相应的线程,选择All可以查看到所有的线程,Frames下面可以查看对应线程
如上所示线程1是Runnable,线程2是blocked
多线程访问同步方法的7种情况(面试常考)
-
两个线程同时访问一个对象的同步方法:方法锁(锁生效)
-
两个线程访问的是两个对象的同步方法:锁无效(两个对象的锁),使用类锁
-
两个线程访问的是synchronized的静态方法:锁生效(类锁)
-
同时访问同步方法与非同步方法:
/** * 只作用于当前方法,不会影响到其他方法 */ public synchronized void method1() { System.out.println("我是加锁的方法,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } public void method2() { System.out.println("我是没加锁的方法,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); }
-
访问同一个对象的不同的普通同步方法:锁生效
/** * 同时访问同一个类的不同的普通同步方法 * 虽然synchronized没有指定锁对象,但是本质上都是this(instance)同一个对象锁 */ public class SynchronizedDifferentMethod7 implements Runnable{ static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7(); @Override public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); } else { method2(); } } public static void main(String[] args) { Thread th1 = new Thread(instance); Thread th2 = new Thread(instance); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } public synchronized void method1() { System.out.println("我是加锁的方法method1,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } public synchronized void method2() { System.out.println("我是加锁的方法method2,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
-
同时访问静态synchronized和非静态的synchronized方法:锁无效
/** * 同时访问静态synchronize的非静态的synchronized方法。 * 类锁和对象锁不是锁住的同一个对象,相互不影响 */ public class SynchronizedStaticAndNormal8 implements Runnable{ static SynchronizedStaticAndNormal8 instance = new SynchronizedStaticAndNormal8(); @Override public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); } else { method2(); } } public static void main(String[] args) { Thread th1 = new Thread(instance); Thread th2 = new Thread(instance); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } public static synchronized void method1() { System.out.println("我是静态加锁的方法1,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } public synchronized void method2() { System.out.println("我是非静态加锁的方法2,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
-
方法抛异常后,会释放锁
/** * 方法抛出异常后,会释放锁。展示不抛出异常前和抛出异常后的对比 * 一旦抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放 */ public class SynchronizedException9 implements Runnable{ static SynchronizedException9 instance = new SynchronizedException9(); @Override public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); } else { method2(); } } public static void main(String[] args) { Thread th1 = new Thread(instance); Thread th2 = new Thread(instance); th1.start(); th2.start(); while (th1.isAlive() || th2.isAlive()){ } System.out.println("finished"); } /** * 抛出异常后,JVM释放锁 */ public synchronized void method1() { System.out.println("我是静态加锁的方法1,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException(); // System.out.println(Thread.currentThread().getName() + " 运行结束。"); } public synchronized void method2() { System.out.println("我是非静态加锁的方法2,我是" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束。"); } }
以上七种情况,可以概况如下:
- 一把锁只能被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
- 每个实例都对应有自己的一把锁,不同实例之间互不影响;但是锁对象是*.class以及synchronized修饰的的是static方法的时候,所有对象共用一把类锁(对应第2、3、4、6种情况)
- 无论是方法正常运行完毕或者方法抛出异常,都会释放锁(对应第7种情况)
Synchronized的特点
锁的类型:自旋锁、互斥锁、可重入锁、中断锁、读写锁
Synchronized是可重入锁,什么是可重入,指的是同一线程的外层函数获取到锁之后,内层函数可以直接再次获取该锁。其原理是加锁次数计数器,在线程拿到锁之后,可以继续持有这把锁,但是对于JVM来说,不是那么简单。我怎么知道该什么时候释放,我拿到这把锁,反复拿了5次,那退出的时候该做什么。
- JVM负责跟踪对象被加锁的次数;
- 有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。
- 任务结束离开,则会执行monitorexit,计数递减,当计数为0的时候,锁完全被释放
Synchronized的粒度:线程而非调用(用3种情况来说明和pthread的区别)pthread是linux中调用为粒度。
-
情况1:证明同一个方法是可重入的。
public class SynchronizedRecursion10 { int a = 0 ; public static void main(String[] args) { SynchronizedRecursion10 instance = new SynchronizedRecursion10(); instance.method1(); } private synchronized void method1() { System.out.println("这是method1, a = " + a); if( a == 0) { a ++ ; method1(); } } }
-
情况2:证明可重入不要求是同一个方法。
/** * 可重入粒度测试:调用类内另外的方法 */ public class SynchronizedOtherMethod11 { public static void main(String[] args) { SynchronizedOtherMethod11 instance = new SynchronizedOtherMethod11(); instance.method1(); } private synchronized void method1() { System.out.println("我是method1"); method2(); } private synchronized void method2() { System.out.println("我是method2"); } }
-
情况3:证明可重入不要求是同一个类中的
/** * 可重入粒度测试:调用父类的方法 */ public class SynchronizedSuperClass12 { public synchronized void doSomething() { System.out.println("我是父类方法"); } } class TestClass extends SynchronizedSuperClass12 { @Override public synchronized void doSomething() { System.out.println("我是子类方法"); super.doSomething(); } public static void main(String[] args) { TestClass testClass = new TestClass(); testClass.doSomething(); } }
以上情况说明只要是需要的锁依然是手中的这把锁,他的可重入性就会显现出来,就不需要显示的释放锁或者重新获取锁
Synchronized性质:不可中断。一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等待下去。相比之下,Lock类,拥有中断的能力,第一点,如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行;第二点,如果我觉得我等待的时间太长了不想在等了,也可以退出。
Synchronized关键字的缺陷
1)效率低:锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程
2)当线程获取到锁,在执行过程中,其他线程也想用该锁时,只能等待当前线程释放。释放情况少,体现在两种情
况:执行完毕、异常(JVM将锁释放),如果要等待IO这种耗时操作或者sleep,又不会中途释放锁,其他线程只能干
巴巴的等着,非常影响程序执行的效率。需要一种机制,遏制让一直在等待,又影响其他线程执行的这些情况。Lock
锁可以
3)不够灵活(读写锁更灵活):加锁和释放锁的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
4)无法知道是否成功获得到锁
Synchronized使用的注意点
1、锁对象不能为空:因为锁信息是保存在对象头中的,若对象是空的,那还哪来的对象头。
2、作用域不宜过大:虽然被synchronized 包裹的代码是线程安全的,但包裹的范围过大会造成程序执行效率低。
3、避免死锁。
如何选择Lock和synchronized关键字
1、建议都不使用,可以使用java.util.concurrent包中的Automic类、countDownLatch等类
2、优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写劲量少的代码就能实现功能。如果需要灵活
的加解锁机制,则使用Lock接口
加锁和释放锁原理
现象
每一个类的实例对应着一把锁,而每一个synchronized方法都必须首先获得调用该方法的类的实例的锁,才能执行。否则线程就会阻塞,而方法一旦执行,就会独占这把锁,直到该方法返回,或者抛出异常,才将锁释放。释放之后,其他被阻塞的线程才能获得这把锁,重新进入可执行的状态
深入JVM看字节码:反编译,monitor指令
概况
synchronize用的锁是java对象头里的一个字段(每一个对象都有一个对象头,对象头可以存储很多信息,其中有一
部分就是用来存储synchronize关键字的锁)
细节:当线程访问代码块的时候必须要得到这把锁,退出整个代码块或者抛出异常的时候必须会释放锁,在JVM规范
中对于jVM实现原理,已经有了说明,它的进入锁和释放锁是基于moniter对象来实现同步方法和同步代码块的,
Monditor对象主要是两个指令,一个是Monditorenter(插入到同步代码块开始的位置),和Monditorexit(退
出,插入到方法结束的时候和退出的时候),jvm的规范要求每一个enter必须要要有exit和他对应,但是可能一个
moniterenter对应多个Monditorexit,退出的时机包括方法结束和抛出异常。每一个对象都有一个Monditor和他
关联,并且moniter被持有后,就会处于锁定状态,当线程执行到Monditorenter指令时,会尝试获取这个对象对应
的Monditor的所有权,也是尝试获取对象的锁。
反编译字节码
(1)目录定义到类所在的文件夹
(2)在命令行,java类编程class对象,使用javac
(3)javap 把class文件解读成字节码文件,java -verbose:加入verbose参数,事无巨细的所有的信息都打印
出来
详细解读Monditorenter和Monditorexit指令:
Monditorenter和Monditorexit在执行的时候会使对象的锁计数加1或者减1,和操作系统中的PV操作(多线程对临
界资源的访问)很像,每一个对象都和一个Monditor相关联,一个moditor的locksware只能被一个线程在同一时间
获得,一个线程在尝试获得与这个对象关联的Monditor所有权的时候(Monditorenter),只会发生以下三种情况之
一
(1)Monditor计数器为0,意味着目前还还没有被获得,这个线程就会立刻获得,然后把计数器加1,之后别人再想
进来就会看到信号,知道它已经被其他线程所持有,加1,意味着当前线程是这个Moditor持有者。(成功获得锁)
(2)当前对象拿到锁的所有权,又重入了,就会导致计数器累加,会变成2,3……随着重入的次数而增加(已经有了这
把锁再次重入的情况)
(3)Monditor已经被其他线程所持有了,当前线程再次获取就会得到现在无法获取的信号,就会进入阻塞状态,知
道Moditor的计数器变为0,才会再次尝试获取这个锁
Monditorexit指令
作用,释放Monditor(Monditor可以理解为锁)的所有权,前提是已经拥有了锁的所有权,释放的过程就是将
Monditor的计数器减1,减完之后变成0就意味着当前线程不在拥有对Monditor的所有权,就是解锁,如果减完之后
不是0,意味着刚才是可重入进来的,所以还继续持有这把锁,最终减到0之后,不仅意味着释放锁了,还意味着刚才
被阻塞的线程,会再次尝试获取对该把锁的所有权