一、synchronized的三种应用方式
1、修饰实例方法,锁是当前实例对象,进入同步代码前要获得当前实例的锁
/** * synchronized修饰实例方法,当前线程的锁是实例对象accountingSync * 当一个线程正在访问一个对象的synchronized实例方法,那么其他线程不能访问该对象的其他synchronized方法 * 一个对象只有一把锁 */ public class AccountingSync implements Runnable { static AccountingSync accountingSync = new AccountingSync(); //共享资源 static int i = 0; static int j = 0; public synchronized void increase() { i++; } @Override public void run() { for(int i =0;i<1000000;i++){ synchronized (this){ increase(); } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(accountingSync); Thread thread2 = new Thread(accountingSync); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
/** * thread1访问实例对象obj1的synchronized方法,thread2访问实例对象obj1的synchronized方法 * 这样是允许的,因为两个实例对象锁并不相同。 * 此时如果两个线程操作数据非共享,线程安全有保证,如果数据共享,线程安全无法保证 * */ public class AccountingSyncBad implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int i = 0; i < 1000000; i++) { increase(); } } public static void main(String[] args) throws InterruptedException{ //new新实例 Thread thread1 = new Thread(new AccountingSyncBad()); //new新实例 Thread thread2 = new Thread(new AccountingSyncBad()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
2、修饰静态方法,锁是当前类的 class对象,进入同步代码前要获得当前类对象的锁
public class AccountingSyncClass implements Runnable{ static int i = 0; /** * synchronized作用于静态方法,锁是当前class对象 */ public static synchronized void increase() { i++; } /** * increase4Obj方法是实例方法,其对象锁是当前实例对象, * 如果别的线程调用该方法,将不会产生互斥现象,毕竟锁对象不同, * 但我们应该意识到这种情况下可能会发现线程安全问题(操作了共享静态变量i)。 */ public synchronized void increase4Obj(){ i++; } @Override public void run() { for (int i = 0; i < 1000000; i++) { increase(); // increase4Obj(); } } public static void main(String[] args) throws InterruptedException{ //new新实例 Thread thread1 = new Thread(new AccountingSyncClass()); //new新实例 Thread thread2 = new Thread(new AccountingSyncClass()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } }
3、修饰代码块
synchronized(this) 锁是当前实例对象,
synchronized(AccountingSync.class) 锁是class对象
二、synchronized代码块底层原理
synchronized代码块是由一对monitorenter和monitorexit指令实现的,Monitor对象是同步的基本实现单元。
现代java虚拟机对sychronized进行了优化,引入了偏斜锁、轻量级锁、重量级锁
三、java虚拟机对Synchronized的优化
JVM优化synchronized运行的机制,当JVM检测到不同的竞争情况时,会自动切换到适合的锁实现
1、当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS操作,在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。
2、有竞争出现时,当有另外的线程试图锁定某个已经被偏斜锁锁定的对象,jvm就会撤销revoke偏斜锁,并切换到轻量级锁。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果成功,就使用轻量级锁,否则继续升级未重量级锁
PS:锁降级也是存在的,当JVM进入SafePoint安全点的时候,会检查是否有闲置的Monitor,然后试图进行降级。