synchronized可修饰普通方法、静态方法和代码块 。
修饰普通方法,锁的是对象,一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。
public synchronized void sayHello(){
}
修饰静态方法,锁的是类(new多个对象都是源于同一个类,同步静态方法仍然互锁)
此类所有的实例对象,都无法同时访问类中的所有同步静态(synchronized static)方法
public synchronized static void sayHello(){
}
修饰代码块,取决于锁的是什么(synchronized(this) synchronized(My.class) )
synchronized(this){
}
synchronized是基于JVM层面的,lock是基于JDK层面的
package com.paddx.test.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
javap 进行class文件反编译
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程都可以尝试去获取这个 monitor 的所有权,所以是非公平锁。
通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
package com.paddx.test.concurrent;
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
synchronized按锁的量级从轻到重分为:无锁---> 偏向锁--->轻量锁---->重量锁 锁只能升级不能降级
偏向锁:如果目前只有一个线程多次获得锁,就没必要下次再用锁的时候重新竞争,轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令,下次还是这个线程来用对象就可以无需验证直接获得锁。如果有多个线程抢用锁,则撤销偏向锁并膨胀为轻量锁。 无阻塞 无自旋 加锁解锁无消耗 适用于无多线程执行同步代码的情况
轻量锁:竞争锁的线程首先需要拷贝对象头中的Mark Word到帧栈的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前线程的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁。如果更新失败,那么意味着有多个线程在竞争。
当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。 有自旋 无阻塞 占用CPU 适用同步代码执行快的情况
重量锁:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。 有阻塞 无自旋
还有其他几种锁类型作为延伸阅读。
互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
自旋锁:spinlock,在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁,虽然CPU的时间被消耗了,但是比线程下文切换时间要少。这个时候使用自旋是划算的。
读写锁:rwlock,区分读和写,处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。
注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写优先于读,当有线程因为等待写锁而进入睡眠时,则后续读者也必须等待
适用于读取数据的频率远远大于写数据的频率的场合。
public class Synchronized {
public void method1(){
synchronized (this) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (this) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序运行");
Synchronized syn = new Synchronized();
// Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn.method2();
}).start();
}
}
结果自然是先执行method1,再执行method2
程序运行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end
public class Synchronized {
public void method1(){
synchronized (Synchronized.class) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (Synchronized.class) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序运行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
锁的是类,那么该类生成的所有实例对象都会被锁住。
程序运行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end
public class Synchronized {
public void method1(){
synchronized (Synchronized.class) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (this) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序运行");
Synchronized syn = new Synchronized();
// Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn.method2();
}).start();
}
}
一个锁的是 .class文件,一个锁的是对象,那么到底这两个方法会互斥执行么?
答案是不会,因为本质上锁的就不是同一个对象!!!
程序运行
method1 start
Method 1 execute
method2 start
Method 2 execute
method2 end
method1 end
public class Synchronized {
public synchronized static void method1(){
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
public synchronized static void method2(){
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
public static void main(String[] args) {
System.out.println("程序运行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
锁加在静态方法上,锁的是class对象,那么new 的新对象自然也会抢占锁
public class Synchronized {
public synchronized static void method1(){
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
public synchronized void method2(){
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
public static void main(String[] args) {
System.out.println("程序运行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
一个锁的class,一个锁的方法,自然不会发生互斥地现象。synchronized加在static方法上,只会锁其他synchronized static方法