明确知道多线程的情况下,不一定。
因为偏向锁在多线程情况下,会涉及到锁撤销,这个时候直接使用自旋锁,JVM启动过程,会有很多线程竞争,比如启动的时候,肯定是多线程的,所以默认情况,启动时候不打开偏向锁,过一段时间再打开。
有一个参数可以配置:BiasedLockingStartupDelay默认是4s钟
一个线程拿20个对象进行加锁,批量锁的重偏向(20个对象),批量锁撤销(变成轻量级锁)(40个对象), 通过Epoch中的值和对应的类对象里面记录的值比较。
synchronized 锁定对象 public class SynchronizedObject implements Runnable { static SynchronizedObject instance = new SynchronizedObject(); final Object object = new Object(); static volatile int i = 0; @Override public void run() { for (int j = 0; j < 1000000; j++) { // 任何线程要执行下面的代码,必须先拿到object的锁 synchronized (object) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } } 锁定方法锁定静态方法相当于锁定当前类
public class SynchronizedStatic implements Runnable { static SynchronizedStatic instance = new SynchronizedStatic(); static volatile int i = 0; @Override public void run() { increase(); } // 相当于synchronized(SynchronizedStatic.class) synchronized static void increase() { for (int j = 0; j < 1000000; j++) { i++; } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }锁定非静态方法相当于锁定该对象的实例或synchronized(this)
public class SynchronizedMethod implements Runnable { static SynchronizedMethod instance = new SynchronizedMethod(); static volatile int i = 0; @Override public void run() { increase(); } void increase() { for (int j = 0; j < 1000000; j++) { // 任何线程要执行下面的代码,必须先拿到object的锁 synchronized (this) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } } 脏读 public class DirtyRead { String name; double balance; public static void main(String[] args) { DirtyRead a = new DirtyRead(); Thread thread = new Thread(() -> a.set("zhangsan", 100.0)); thread.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); } public synchronized void set(String name, double balance) { this.name = name; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; } // 如果get方法不加synchronized关键字,就会出现脏读情况 public /*synchronized*/ double getBalance(String name) { return this.balance; } }其中的getBalance方法,如果不加synchronized,就会产生脏读的问题。
可重入锁一个同步方法可以调用另外一个同步方法,
一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁(可重入锁)
子类synchronized,如果调用父类的synchronize方法:super.method(),如果不可重入,直接就会死锁。
程序在执行过程中,如果出现异常,默认情况锁会被释放 ,所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适, 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常。
示例见:
SynchronizedException.java
synchronized的底层实现在早期的JDK使用的是OS的重量级锁
后来的改进锁升级的概念:
synchronized (Object)
markword 记录这个线程ID (使用偏向锁)
如果线程争用:升级为 自旋锁
10次自旋以后,升级为重量级锁 - OS
所以:
执行时间短(加锁代码),线程数少,用自旋
执行时间长,线程数多,用系统锁
synchronized不能锁定String常量,Integer,Long等基础类型见示例: