为了方便记忆,将锁作以下的分类html
1、对象锁java
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(本身指定锁对象)jvm
1.代码块形式:手动指定锁定对象,也但是是this,也能够是自定义的锁ide
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence = new SynchronizedObjectLock(); @Override public void run() { // 同步代码块形式——锁为this,两个线程使用的锁是同样的,线程1必需要等到线程0释放了该锁后,才能执行 synchronized (this) { System.out.println("我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } }
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束函数
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence = new SynchronizedObjectLock(); // 建立2把锁 Object block1 = new Object(); Object block2 = new Object(); @Override public void run() { // 这个代码块使用的是第一把锁,当他释放后,后面的代码块因为使用的是第二把锁,所以能够立刻执行 synchronized (block1) { System.out.println("blocl1锁,我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("blocl1锁,"+Thread.currentThread().getName() + "结束"); } synchronized (block2) { System.out.println("blocl2锁,我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("blocl2锁,"+Thread.currentThread().getName() + "结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); }
输出结果:
blocl1锁,我是线程Thread-0
blocl1锁,Thread-0结束
blocl2锁,我是线程Thread-0 // 能够看到当第一个线程在执行完第一段同步代码块以后,第二个同步代码块能够立刻获得执行,由于他们使用的锁不是同一把
blocl1锁,我是线程Thread-1
blocl2锁,Thread-0结束
blocl1锁,Thread-1结束
blocl2锁,我是线程Thread-1
blocl2锁,Thread-1结束性能
2.方法锁形式:synchronized修饰普通方法,锁对象默认为thisthis
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence = new SynchronizedObjectLock(); @Override public void run() { method(); } 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() + "结束"); } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } }
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束spa
2、类锁线程
指synchronize修饰静态的方法或指定锁对象为Class对象3d
1.synchronize修饰静态方法
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence1 = new SynchronizedObjectLock(); static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override public void run() { method(); } // synchronized用在普通方法上,默认的锁就是this,当前实例 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() + "结束"); } public static void main(String[] args) { // t1和t2对应的this是两个不一样的实例,因此代码不会串行 Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }
输出结果:
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence1 = new SynchronizedObjectLock(); static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override public void run() { method(); } // synchronized用在静态方法上,默认的锁就是当前所在的Class类,因此不管是哪一个线程访问它,须要的锁都只有一把 public static synchronized void method() { System.out.println("我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束"); } public static void main(String[] args) { Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
2.synchronized指定锁对象为Class对象
public class SynchronizedObjectLock implements Runnable { static SynchronizedObjectLock instence1 = new SynchronizedObjectLock(); static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override public void run() { // 全部线程须要的锁都是同一把 synchronized(SynchronizedObjectLock.class){ System.out.println("我是线程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
3、思考
1.两个线程同时访问1个对象的同步方法
2.两个线程同时访问2个对象的同步方法
3.两个线程访问的是synchronized静态方法
4.两个线程同时访问同步(被synchronized修饰)和非同步(未被snychronized修饰)方法
5.两个线程同时访问1个对象的不一样的普通同步方法
6.两个线程同时访问一个静态的synchronized方法和非静态的synchronized方法
7.方法抛出异常后,会释放锁吗?
核心思想:
1.一把锁只能同时被一个线程获取,没有难道锁的线程只能等待(对应上面的1,5)
2.每一个实例都对应有本身的一把锁(this),不一样实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,全部对象公用同一把锁(对应上面的2,3,4,6)
3.synchronized修饰的方法,不管方法正常执行完毕仍是抛出异常,都会释放锁(对应上面的7)
4、synchronized的性质
1.可重入性
概念:指同一个线程外层函数获取到锁以后,内层函数能够直接使用该锁
好处:避免死锁,提高封装性(若是不可重入,假设method1拿到锁以后,在method1中又调用了method2,若是method2没办法使用method1拿到的锁,那method2将一直等待,可是method1因为未执行完毕,又没法释放锁,就致使了死锁,可重入正好避免这这种状况)
粒度:线程而非调用(用3中状况来讲明与pthread的区别)
1)状况1:证实同一个方法是可重入的(递归)
public class SynchronizedDemo2 { int a = 0; public static void main(String[] args) { new SynchronizedDemo2().method1(); } public synchronized void method1() { System.out.println("a=" + a); if (a == 0) { a++; method1(); } } }
输出结果:
a=0
a=1
2)状况2:证实可重入不要求是同一个方法
public class SynchronizedDemo2 { public static void main(String[] args) { new SynchronizedDemo2().method1(); } public synchronized void method1() { System.out.println("method1"); method2(); } public synchronized void method2() { System.out.println("method2"); } }
输出结果:
method1
method2
3)状况3:证实可重入不要求是同一个类中
public class SynchronizedDemo2 { public synchronized void method1() { System.out.println("父类method1"); } } class SubClass extends SynchronizedDemo2 { public synchronized void method1() { System.out.println("子类method1"); super.method1(); } public static void main(String[] args) { new SubClass().method1(); } }
输出结果:
子类method1
父类method1
2.不可中断性
概念:若是这个锁被B线程获取,若是A线程想要获取这把锁,只能选择等待或者阻塞,直到B线程释放这把锁,若是B线程一直不释放这把锁,那么A线程将一直等待。
相比之下,将来的Lock类,能够拥有中断的能力(若是一个线程等待锁的时间太长了,有权利中断当前已经获取锁的线程的执行,也能够退出等待)
5、深刻原理
1.加锁和释放锁的原理:现象、时机(内置锁this)、深刻JVM看字节码(反编译看monitor指令)
Lock lock = new ReentrantLock(); public synchronized void method1() { System.out.println("synchronized method1"); } public void method2() { lock.lock(); try { System.out.println("lock method2"); } finally { lock.unlock(); }
}
method1与method2等价,synchronized至关于先获取锁,执行结束/抛出异常后,释放锁。
深刻JVM看字节码,建立以下的代码:
public class SynchronizedDemo2 { Object object = new Object(); public void method1() { synchronized (object) { } } }
使用javac命令进行编译生成.class文件 >javac SynchronizedDemo2.java 使用javap命令反编译查看.class文件的信息 >javap -verbose SynchronizedDemo2.class 获得以下的信息:
关注红色方框里的monitorenter和monitorexit便可。
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程得到,一个对象在尝试得到与这个对象相关联的Monitor锁的全部权的时候,monitorenter指令会发生以下3中状况之一:
1)monitor计数器为0,意味着目前尚未被得到,那这个线程就会马上得到而后把锁计数器+1,一旦+1,别的线程再想获取,就须要等待
2)若是这个monitor已经拿到了这个锁的全部权,又重入了这把锁,那锁计数器就会累加,变成2,而且随着重入的次数,会一直累加
3)这把锁已经被别的线程获取了,等待锁释放
monitorexit指令:释放对于monitor的全部权,释放过程很简单,就是讲monitor的计数器减1,若是减完之后,计数器不是0,则表明刚才是重入进来的,当前线程还继续持有这把锁的全部权,若是计数器变成0,则表明当前线程再也不拥有该monitor的全部权,即释放锁。
2.可重入原理:加锁次数计数器
jvm会负责跟踪对象被加锁的次数
线程第一次得到所,计数器+1,当锁重入的时候,计数器会递增
当任务离开的时候(一个同步代码块的代码执行结束),计数器会减1,当减为0的时候,锁被彻底释放。
3.保证可见性的原理:内存模型
访问连接 https://www.cnblogs.com/xyabk/p/10894384.html
6、synchronized的缺陷
效率低:锁的释放状况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock能够中断和设置超时
不够灵活:加锁和释放的时机单一,每一个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
没法知道是否成功得到锁,相对而言,Lock能够拿到状态,若是成功获取锁,....,若是获取失败,.....
7、Lock对synchronized的弥补
Lock类这里不作过多解释,主要看上面红色方框里面的4个方法
lock():加锁
unlock():解锁
tryLock():尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil):尝试获取锁,能够设置超时
8、注意
1.锁对象不能为空,由于锁的信息都保存在对象头里
2.做用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
3.避免死锁
4.在能选择的状况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各类各样的类,若是不用该包下的类,在知足业务的状况下,可使用synchronized关键,由于代码量少,避免出错
9、思考
1.多个线程等待同一个snchronized锁的时候,JVM如何选择下一个获取锁的线程?
2.Synchronized使得同时只有一个线程能够执行,性能比较差,有什么提高的方法?
3.我想更加灵活地控制锁的释放和获取(如今释放锁和获取锁的时机都被规定死了),怎么办?
4.什么是锁的升级和降级?什么事JVM里的偏斜锁、轻量级锁、重量级锁?