synchronized
1、synchronized的几种基本用法:
public class SynchronizedTest1 { private Object lock = new Object(); /** * ①对于普通统发方法,锁是当前实例对象 */ public synchronized void f() { } /** * ②对于静态同步方法,锁是当前类的Class对象 */ public synchronized static void g() { } /** * ③对于普通方法快,锁是synchronized括号里配置的对象 */ public void p() { synchronized (lock) { } } public void q() { synchronized (this) { } }
public void k() {
synchronized(SynchronizedTest1.class) {
}
} }
ps: synchronized方法(比如①)和synchronized代码块(比如③)的性能相比较,①比②性能要低,所以尽量用synchronized代码块
2、加锁的错误用法
/** * 加锁的错误示例 * 示例中两个加锁的方法,它们的锁对象不是同一个,对于方法f()的锁对象是this,也就是SynchronizedTest2 * 对于方法g()的锁对象是Object,所以这两个方法的同步是互相独立的 */ public class SynchronizedTest2 { private Object lock = new Object(); public synchronized void f() { for(int i = 0; i < 5; i++) { System.out.println("f(" + i + ")"); Thread.yield(); } } public void g() { synchronized (lock) { for(int i = 0; i < 5; i++) { System.out.println("g(" + i + ")"); Thread.yield(); } } } public static void main(String[] args) { final SynchronizedTest2 test2 = new SynchronizedTest2(); new Thread() { @Override public void run() { test2.f(); } }.start(); test2.g(); } }
3、锁的升级与对比
JavaSE6.0 锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会锁着锁竞争而升级。
锁可以升级而不能降级
3.1 偏向锁:
为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和帧栈中的锁记录里存储偏向的线程ID,以后该线程在进入和退出同步块时不需要CAS操作来加锁和解锁,只需
要简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功表示线程已经获得了锁。
3.2 轻量级锁:
线程在执行同步块之前,JVM会先在当前线程的帧栈中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方成为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark
Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
4、synchronized的可重入性(对比重入锁:ReentrantLock)
什么叫做锁的重入性? 当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在
一个线程调用synchronized方法的
同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。
synchronized是隐式的支持重进入。
Demo1:
public class SynchronizedTest3 implements Runnable{ int a = 0; int b = 0; public static void main(String[] args) throws InterruptedException { SynchronizedTest3 test = new SynchronizedTest3(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); //启动两个线程跑 t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(test.a); System.out.println(test.b); } //这个的锁对象是当前实例对象 public synchronized void increase() { b++; } @Override public void run() { for(int i = 0; i < 1000; i++) { //这个的锁对象也是当前实例对象 synchronized (this) { a++; increase(); } } } } /* output: 2000 2000 */
Demo2:
public class Widget { public synchronized void doSomething() { ...... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { //业务操作 super.doSomething(); } }
5、线程的中断与synchroinzed 参考:https://www.cnblogs.com/happyflyingpig/p/9716055.html
事实上线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它
就保存等待,即使调用中断线程的方法,也不会生效。
6、等待唤醒机制与synchroinzed
所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,
这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),
而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。
synchronized (obj) { obj.wait(); obj.notify(); obj.notifyAll(); }
需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程
调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在
相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。
参考:
【1】《Think in Java》第四版
【2】《Java核心技术》卷Ⅰ
【3】《Java并发编程的艺术》,方腾飞
【4】《Java高并发程序设计》,葛一鸣
【5】《Java并发编程实战》,童云兰
【6】博客,https://blog.csdn.net/javazejian/article/details/72828483
【7】博客,http://www.cnblogs.com/skywang12345/p/3479202.html