Synchronized关键字的使用以及基本原理
一、Synchronized的使用场景
首先要清楚synchronized的使用原理
1. 作用于方法时,锁住的是对象的实例(this); 2. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8 则 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程; 3. synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。 它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
|
编码测试,使用场景模拟如下
1、修饰类中的普通方法
@Slf4j(topic = "ellison") public class BasicLock {
public synchronized void x(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("x"); }
public synchronized void y(){ log.debug("y"); } public void z(){ log.debug("z"); }
public static void main(String[] args) { BasicLock basicLock = new BasicLock(); new Thread(()->{ log.debug("start"); basicLock.x(); },"t1").start(); new Thread(()->{ log.debug("start"); basicLock.y(); },"t2").start(); } }
|
2、修饰类中的普通方法,但是锁的是不同的对象
@Slf4j(topic = "ellison") public class BasicLock1 {
public synchronized static void x(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("x"); }
public synchronized void y(){ log.debug("y"); } public void z(){ log.debug("z"); }
public static void main(String[] args) { BasicLock basicLock = new BasicLock(); BasicLock basicLock1 = new BasicLock(); new Thread(()->{ log.debug("start"); basicLock.x(); },"t1").start();
new Thread(()->{ log.debug("start"); basicLock1.y(); },"t2").start(); } }
|
3、修饰类中static修饰的方法
@Slf4j(topic = "ellison") public class BasicLock1 {
public synchronized static void x(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("x"); }
public synchronized void y(){ log.debug("y"); } public void z(){ log.debug("z"); }
public static void main(String[] args) { BasicLock1 basicLock1 = new BasicLock1(); new Thread(()->{ log.debug("t1--start"); basicLock1.x(); },"t1").start();
new Thread(()->{ log.debug("t2--start"); basicLock1.y(); },"t2").start(); } }
|
方法之间的性能测试
测试方法的性能,这里提供一个测试工具 JMH ,关于JMH的使用。
推荐博客 https://www.zhihu.com/question/276455629/answer/1259967560
mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=com.ellison.jmh -DartifactId=pei -Dversion=1.0.0-SNAPSHOT -DarchetypeCatalog=local
|
4、synchronized 关键字的线程安全问题
@Slf4j(topic = "ellison") public class Demo1 { private int count = 10; private Object object = new Object(); public void test(){ synchronized (object){ count--; log.debug(Thread.currentThread().getName() + " count = " + count); } }
public static void main(String[] args) { Demo1 demo1 = new Demo1(); new Thread(demo1::test, "t1").start();
} }
@Slf4j(topic = "ellison") public class Demo2 { private int count = 10; public void test(){ synchronized (this){ count--; log.debug(Thread.currentThread().getName() + " count = " + count); } } public static void main(String[] args) { Demo2 demo2 = new Demo2(); new Thread(demo2::test,"t1").start(); Demo2 demo21 = new Demo2(); new Thread(demo21::test, "t2").start(); } }
@Slf4j(topic = "ellison") public class Demo3 { private int count = 10; public synchronized void test(){ count--; log.debug(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { Demo3 demo3 = new Demo3(); new Thread(demo3::test,"t1").start();
Demo3 demo31 = new Demo3(); new Thread(demo31::test, "t3").start(); }
}
@Slf4j(topic = "ellison") public class Demo4 { private static int count = 10; public synchronized static void test(){ count--; log.debug(Thread.currentThread().getName() + " count = " + count); } public static void test2(){ synchronized (Demo4.class){ count--; } } }
|
5、锁对象的改变
@Slf4j(topic = "enjoy") public class Demo1 { Object o = new Object(); public void test(){ synchronized (o) { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(Thread.currentThread().getName()); } } }
public static void main(String[] args) { Demo1 demo = new Demo1(); new Thread(demo :: test, "t1").start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
Thread t2 = new Thread(demo :: test, "t2"); demo.o = new Object(); t2.start(); }
}
@Slf4j(topic = "ellison") public class Demo3 { int count = 0; public synchronized void test1(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } count ++; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }
public void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this) { count ++; } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
6、脏读问题
@Slf4j(topic = "enjoy") public class Demo {
String name;
double balance;
public synchronized void set(String name,double balance){ this.name = name; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; }
public synchronized double getBalance(String name){ return this.balance; }
public static void main(String[] args) { Demo demo = new Demo();
new Thread(()->demo.set("zl",100.0)).start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(demo.getBalance("zl")+"");
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(demo.getBalance("zl")+""); }
}
|
7、Synchronized 的可重入
@Slf4j(topic = "enjoy") public class Demo {
synchronized void test1(){ log.debug("test1 start........."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } test2(); }
synchronized void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("test2 start......."); } public static void main(String[] args) { Demo demo= new Demo(); demo.test1(); }
}
@Slf4j(topic = "enjoy") public class Demo { synchronized void test(){ log.debug("demo test start........"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("demo test end........"); } public static void main(String[] args) { new Demo2().test(); }
} @Slf4j(topic = "enjoy") class Demo2 extends Demo { @Override synchronized void test(){ log.debug("demo2 test start........"); super.test(); log.debug("demo2 test end........"); } }
|
8、synchronized 和异常的关系
@Slf4j(topic = "enjoy") public class Demo { Object o = new Object();
int count = 0;
void test(){ synchronized(o) { log.debug(Thread.currentThread().getName() + " start......"); while (true) { count++; log.debug(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
if (count == 5) { int i = 1 / 0; } } } }
public static void main(String[] args) { Demo demo11 = new Demo();
new Thread(()->{ demo11.test(); },"t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ demo11.test(); }, "t2").start(); }
}
|
9、volatile 关键字
@Slf4j(topic = "enjoy") public class Demo { boolean running = true; List<String> list = new ArrayList<>();
public void test(){ log.debug("test start..."); boolean flag =running; while (running){
} log.debug("test end..."); }
public static void main(String[] args) { Demo demo = new Demo(); new Thread(demo :: test,"t1").start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } demo.running = false; }
}
|
10、volatile 关键字并不能保证原子性
对volatile 修饰的变量进行单词操作时,具有原子性。但进行类似++这种复合操作时,就不具有原子性了
@Slf4j(topic = "enjoy") public class Demo { volatile int count = 0; public void test(){ for (int i = 0; i < 10000; i++) { count ++; } }
public static void main(String[] args) { Demo demo = new Demo(); List<Thread> threads = new ArrayList(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo::test, "t-" + i)); } threads.forEach((o)->o.start()); threads.forEach((o)->{ try { o.join(); } catch (Exception e) { e.printStackTrace(); } }); log.debug(demo.count+""); }
}
|
11、synchronized既保证了原子性又保证了可见性
@Slf4j(topic = "enjoy") public class Demo { int count = 0; public synchronized void test(){ for (int i = 0; i < 10000; i++) { count ++; } }
public static void main(String[] args) { Demo demo = new Demo(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo::test, "thread-" + i)); } threads.forEach((o)->o.start()); threads.forEach((o)->{ try { o.join(); } catch (Exception e) { e.printStackTrace(); } }); log.debug(demo.count+""); }
}
|
二、Synchronized 的底层原理
Synchronized 在 JVM 里的实现都是基于进入和退出 Monitor 对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的 MonitorEnter 和 MonitorExit 指令来实现。
对同步块,MonitorEnter 指令插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象 Monitor 的所有权,即尝试获得该对象的锁,而 monitorExit 指令则插入在方法结束处和异常处,JVM 保证每个 MonitorEnter 必须有对应的 MonitorExit。
对同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令 monitorenter和 monitorexit 来实现,相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。
JVM 就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取 monitor,获取成功之后才能执行方法体,方法执行完后再释放 monitor。在方法执行期间,其他任何线程都无法再获得同一个 monitor 对象。
synchronized 使用的锁是存放在 Java 对象头里面,具体位置是对象头里面的 MarkWord, MarkWord 里默认数据是存储对象的 HashCode 等信息,但是会随着对象的运行改变而发生变化,不同的锁状态对应着不同的记录存储方式。在具体优化上,从 1.6 开始引入了偏向锁、 自旋锁等机制提升性能。
代码地址:
GitHub:https://github.com/PeiAlan/SyncTest.git
Gitee:https://gitee.com/ellisonpei/sync-test.git