synchronized
工作原理及使用小结为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用
synchronized
加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题
本篇将集中在synchronized
关键字的工组原理以及使用方式上
以一个case进行分析,源码如下
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
在加锁的代码块, 多了一个 monitorenter
, monitorexit
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
锁
谈到
synchronized
就不可避免的要说到锁这个东西,基本上在网上可以搜索到一大批的关于偏向锁,轻量锁,重量锁的讲解文档,对这个东西基本上我也不太理解,多看几篇博文之后,简单的记录一下
先抛一个结论: 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能
获取过程
释放过程
“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。 但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。 在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
简单来讲,单线程时,使用偏向锁,如果这个时候,又来了一个线程访问这个代码块,那么就要升级为轻量锁,如果这个线程在访问代码块同时,又来了一个线程来访问这个代码块,那么就要升级为重量锁了。下面更多的显示了这些变动时,标记位的随之改变
一个case: TestDemo方法定义如下
public class TestDemo {
public synchronized void a() {
// ...
}
public synchronized void b() {
// ...
}
public static synchronized void c() {
// ...
}
public static synchronized void d() {
// ...
}
public void e() {
// ...
}
public void f() {
synchronized(this) {
// ....
}
}
public void g() {
synchronized(this) {
// ....
}
}
}
对上面的问题,核心的一点就是synchronized
是否只作用于修饰的代码块or方法上
TestDemo的具体实现如下
public class TestDemo {
public synchronized void a(String msg) {
System.out.println(Thread.currentThread().getName() + ":a() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":a() after: " + msg);
}
public synchronized void b(String msg) {
System.out.println(Thread.currentThread().getName() + ":b() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":b() after: " + msg);
}
public static synchronized void c(String msg) {
System.out.println(Thread.currentThread().getName() + ":c() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":c() after: " + msg);
}
public static synchronized void d(String msg) {
System.out.println(Thread.currentThread().getName() + ":d() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":d() after: " + msg);
}
public void e(String msg) {
System.out.println(Thread.currentThread().getName() + ":e() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":e() after: " + msg);
}
public void f(String msg) {
System.out.println(Thread.currentThread().getName() + ":f() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":f() after: " + msg);
}
public void g(String msg) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ":a() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":a() after: " + msg);
}
}
public void h(String msg) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ":h() before");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":h() after: " + msg);
}
}
}
测试一 实例加锁方法的访问测试
/**
* 非静态同步方法测试
*/
private void nonStaticSynFun() throws InterruptedException {
TestDemo testDemo = new TestDemo();
Thread thread1 = new Thread(()->testDemo.a("访问同一加锁方法"), "thread1");
Thread thread2 = new Thread(()->testDemo.a("访问同一加锁方法"), "thread2");
System.out.println("---两个线程,访问同一个加锁方法开始---");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("---两个线程,访问同一个加锁方法结束---\n");
//
TestDemo testDemo2 = new TestDemo();
thread1 = new Thread(()->testDemo.a("访问第一个实例同一加锁方法"), "thread1");
thread2 = new Thread(()->testDemo2.a("访问第二个实例同一加锁方法"), "thread2");
System.out.println("---两个线程,访问两个实例同一个加锁方法开始---");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("---两个线程,访问两个同一个加锁方法结束---\n");
//
thread1 = new Thread(()->testDemo.a("访问两个加锁方法"), "thread1");
thread2 = new Thread(()->testDemo.b("访问两个加锁方法"), "thread2");
System.out.println("---两个线程,访问两个加锁方法开始---");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("---两个线程,访问两个加锁方法结束---\n");
//
thread1 = new Thread(()->testDemo.a("访问加锁实例方法"), "thread1");
thread2 = new Thread(()->TestDemo.c("访问加锁静态方法"), "thread2");
System.out.println("---两个线程,访问实例和静态加锁方法开始---");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("---两个线程,访问实例和静态加锁方法结束---\n");
}
@Test
public void testNoStaticSynFun() throws InterruptedException {
for(int i = 0; i < 2000; i++) {
nonStaticSynFun();
}
}
上面的测试case主要覆盖:
输出结果如下
---两个线程,访问同一个加锁方法开始---
thread1:a() before
thread1:a() after: 访问同一加锁方法
thread2:a() before
thread2:a() after: 访问同一加锁方法
---两个线程,访问同一个加锁方法结束---
---两个线程,访问两个实例同一个加锁方法开始---
thread1:a() before
thread2:a() before
thread2:a() after: 访问第二个实例同一加锁方法
thread1:a() after: 访问第一个实例同一加锁方法
---两个线程,访问两个同一个加锁方法结束---
---两个线程,访问两个加锁方法开始---
thread1:a() before
thread1:a() after: 访问两个加锁方法
thread2:b() before
thread2:b() after: 访问两个加锁方法
---两个线程,访问两个加锁方法结束---
---两个线程,访问实例和静态加锁方法开始---
thread1:a() before
thread2:c() before
thread2:c() after: 访问加锁静态方法
thread1:a() after: 访问加锁实例方法
---两个线程,访问实例和静态加锁方法结束---
验证结果:
测试case二: 静态加锁方法测试
private void staticSynFun() throws InterruptedException {
Thread thread1 = new Thread(() -> TestDemo.c("访问加锁静态方法"), "thread1");
Thread thread2 = new Thread(() -> TestDemo.c("访问加锁静态方法"), "thread2");
System.out.println("---两个线程,访问静态加锁方法开始---");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("---两个线程,访问静态加锁方法结束---\n");
//
TestDemo testDemo1 = new TestDemo(), testDemo2 = new TestDemo();
thread1 = new Thread(() -> testDemo1.c("访问加锁静态方法"), "thread1");
thread2 = new Thread(() -> testDemo2.d("访问加锁静态方法"), "thread2");
Thread thread3 = new Thread(() -> testDemo1.a("访问加锁实例方法"), "thread3");
System.out.println("---两个线程,访问不同实例的静态加锁方法开始---");
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("---两个线程,访问不同实例的静态加锁方法结束---\n");
}
@Test
public void testStaticSynFunc() throws InterruptedException {
for (int i = 0; i < 2000; i++) {
staticSynFun();
}
}
上面的测试主要覆盖
输出结果如下
---两个线程,访问静态加锁方法开始---
thread1:c() before
thread1:c() after: 访问加锁静态方法
thread2:c() before
thread2:c() after: 访问加锁静态方法
---两个线程,访问静态加锁方法结束---
---两个线程,访问不同实例的静态加锁方法开始---
thread1:c() before
thread3:a() before
thread1:c() after: 访问加锁静态方法
thread2:d() before
thread3:a() after: 访问加锁实例方法
thread2:d() after: 访问加锁静态方法
---两个线程,访问不同实例的静态加锁方法结束---
验证结果:
测试case三: 同步代码块
基本上和上面的相同,同步代码块分为静态同步代码块(共享类锁);非静态同步代码块(共享实例锁)
synchronized
三中使用姿势,修饰静态方法,实例方法,(静态/非静态)代码块synchronized
底层主要是通过偏向锁,轻量级锁和重量级锁组合来实现线程同步的功能