深入浅出聊聊synchronized
最近在准备寒假实习的面试,synchronized傻傻搞不明白,相信很多小伙伴像我一开始一样,所以就有了这篇博客,对大家有帮助的话,可以点个关注或者点个赞哦~
目录
一.简介
- 修饰普通同步方法:
- 修饰静态同步方法:
- 修饰同步代码块:
感觉很抽象,好,上代码!
修饰普通同步方法
不同的实例,锁就不一样,锁不共享,只能一个一个执行,实现同步操作;
/**
* 修饰同步方法,锁的是当前实例对象
*/
public class SynchronizedMethodExample {
private int count = 0;
// 修饰普通同步方法 - 锁的是当前实例对象
public synchronized void increment() {
System.out.println(Thread.currentThread().getName() + " 进入同步方法");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + " 修改count: " + count);
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== 1. 修饰普通同步方法 ===");
SynchronizedMethodExample example = new SynchronizedMethodExample();
// 创建多个线程操作同一个对象
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
example.increment();
}
}, "线程A");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
example.increment();
}
}, "线程B");
t1.start();
t2.start();
Thread.sleep(10000);//主线程先睡眠,防止主线程执行完了,子线程还没执行完
System.out.println("最终结果: " + example.getCount()); // 应该是6
}
}
大家可以直接复制代码自己去跑一跑,我这里贴出运行结果

可以看到,每一次都是一个线程调用该方法,实现count变量值的修改,大家可以把syncronized关键字去掉,跑一跑看看结果,这里我直接贴出结果:

可以看到,出现了一种情况,就是一个线程进入了这个方法,在还未修改count变量的值前,另一个线程也进来了,这种情况是非常危险的,即所谓的线程安全问题产生了!
修饰静态方法
该类创建的所有对象都共享这把锁,不会出现阻塞情况;
class SimpleCounter {
// 静态同步方法 - 全类共用一把锁
public static synchronized void staticTask(String threadName) {
System.out.println(threadName + " 进入静态方法 ⚡");
try { Thread.sleep(2000); } catch (Exception e) {}
System.out.println(threadName + " 离开静态方法 ✅");
}
// 普通同步方法 - 每个对象有自己的锁
public synchronized void instanceTask(String threadName) {
System.out.println(threadName + " 进入实例方法 🔒");
try { Thread.sleep(1000); } catch (Exception e) {}
System.out.println(threadName + " 离开实例方法 ✅");
}
}
public class VerySimpleExample {
public static void main(String[] args) {
SimpleCounter obj1 = new SimpleCounter();
SimpleCounter obj2 = new SimpleCounter();
System.out.println("=== 场景1:静态方法 - 会阻塞 ===");
new Thread(() -> SimpleCounter.staticTask("线程A")).start();
new Thread(() -> SimpleCounter.staticTask("线程B")).start();
// 等上面执行完
try { Thread.sleep(3000); } catch (Exception e) {}
System.out.println("\n=== 场景2:实例方法 - 不同对象不会阻塞 ===");
new Thread(() -> obj1.instanceTask("线程C-obj1")).start();
new Thread(() -> obj2.instanceTask("线程D-obj2")).start();
// 等上面执行完
try { Thread.sleep(3000); } catch (Exception e) {}
System.out.println("\n=== 场景3:实例方法 - 同一对象会阻塞 ===");
new Thread(() -> obj1.instanceTask("线程E-obj1")).start();
new Thread(() -> obj1.instanceTask("线程F-obj1")).start();
}
}
运行结果:

我们看场景二,obj2在obj1还没有执行完就能进入该方法,因为刚刚已经说过,该类创建的实例都共享一把锁,好比都能打开这个房间的钥匙;而场景3中,都是obj1这个对象,属于同一实例,因而出现了阻塞同步执行;
修饰同步代码块
和普通同步方法很类似
package 并发.Synchronized;
public class SimpleSyncBlock {
private int count = 0;
private final Object lock = new Object(); // 自定义锁对象
public void addCount() {
// 同步代码块 - 使用自定义的lock对象作为锁
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 进入同步块");
// 模拟一些工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + " 修改count为: " + count);
System.out.println(Thread.currentThread().getName() + " 离开同步块");
}
}
public static void main(String[] args) {
SimpleSyncBlock example = new SimpleSyncBlock();
// 创建3个线程同时调用addCount方法
Thread t1 = new Thread(() -> example.addCount(), "线程A");
Thread t2 = new Thread(() -> example.addCount(), "线程B");
Thread t3 = new Thread(() -> example.addCount(), "线程C");
System.out.println("=== 开始测试同步代码块 ===");
t1.start();
t2.start();
t3.start();
try {
Thread.sleep(5000);//避免主线程都执行完了三个子线程还没执行完;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("最终count值: " + example.count);
}
}
可以看到三个线程依次进入执行

二.synchronized的特性
-
原子性
public class AtomicityExample {
private int balance = 1000; // 账户余额
// 没有同步 - 非原子操作
public void withdrawUnsafe(int amount) {
if (balance >= amount) {
// 这里可能被其他线程打断,导致数据错误
try {
Thread.sleep(100); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,余额: " + balance);
} else {
System.out.println("余额不足");
}
}
// 使用synchronized保证原子性
public synchronized void withdrawSafe(int amount) {
if (balance >= amount) {
try {
Thread.sleep(100); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,余额: " + balance);
} else {
System.out.println("余额不足");
}
}
public static void main(String[] args) throws InterruptedException {
AtomicityExample account = new AtomicityExample();
System.out.println("=== 非原子操作(会出现问题)===");
// 创建多个线程同时取款
for (int i = 0; i < 5; i++) {
new Thread(() -> account.withdrawUnsafe(200), "线程" + i).start();
}
Thread.sleep(2000);
System.out.println("\n=== 原子操作(synchronized保证)===");
account.balance = 1000; // 重置余额
for (int i = 0; i < 5; i++) {
new Thread(() -> account.withdrawSafe(200), "线程" + i).start();
}
}
}
执行结果对比

-
可见性
一个线程修改了共享变量,其他线程能立即看到修改后的值。
package 并发.Synchronized.特性;
public class SyncDemo {
private static boolean flag = false;
private static final Object lock = new Object(); // 锁对象
public static void main(String[] args) throws InterruptedException {
System.out.println("=== synchronized可见性演示 ===");
// 线程A:修改flag(使用同步代码块)
Thread threadA = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ✅ 同步代码块 - 锁对象是lock,获取到对象锁,就执行括号内的方法
synchronized (lock) {
flag = true;
System.out.println("线程A: 在同步代码块内设置 flag = true");
}
});
// 线程B:读取flag(使用同步代码块)
Thread threadB = new Thread(() -> {
System.out.println("线程B: 开始检查flag");
while (true) {
// ✅ 同步代码块 - 锁对象是lock,执行完一次就释放
synchronized (lock) {
System.out.println("------线程B在执行------");
if (flag) {
System.out.println("线程B: 在同步代码块内检测到flag变为true!");
break;
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadB.start();
threadA.start();
threadA.join();
threadB.join();
System.out.println("演示完成!");
}
}
执行结果:

解释:两个线程依次启动,但是由于线程A会睡眠1s,所以对象锁是B先拿到,然后线程B没每在循环体内循环一次,当执行完之后,就会释放锁,又拿到锁,又执行,就在锁释放与锁获取的间隙,线程A争抢到锁,完成修改,然后通过内存屏障,将flag的值刷新到主内存,线程B每次去主内存去拿值(这里就涉及java的内存模型JMM),当拿到更新之后的值后,就退出循环;
可见性原理:
- Load屏障:执行refresh,从其它处理器的高速缓冲、主内存,加载数据到自己的高速缓存、保证数据是最新的;
- Store屏障:执行flush操作,自己处理器更新的变量的值,刷新到高速缓存、主内存去;
int a = 0;
synchronize (this){ //monitorenter
// Load内存屏障
int b = a; //读,通过Load内存屏障,强制执行refresh,保证读到最新的
a = 10; //写,释放锁时会通过Store,强制flush到高速缓存或主内存
} //monitorexit
//Store内存屏障
synchornized加锁和自动释放锁是基于moniter监视器实现,底层是c++实现,如下的代码片段,他就是上锁和释放锁的字节码反编译看到的,为什么释放两次呢,因为隐式实现try-catch-finally避免代码抛异常而没有释放锁,这就解释了上面的代码的moniterenter和moniterexit操作;

-
有序性
-
可重入性
public class RenentrantDemo {
// 锁对象
private static Object obj = new Object();
public static void main(String[] args) {
// 自定义Runnable对象
Runnable runnable = () -> {
// 使用嵌套的同步代码块
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第一次获取锁资源...");
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第二次获取锁资源...");
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第三次获取锁资源...");
}
}
}
};
可以再参考一下这些文章:
synchronized总结:怎么保证可见性、有序性、原子性?
https://www.cnblogs.com/firsthelloworld/p/18826929
https://blog.csdn.net/weixin_44978801/article/details/147314728
三.锁升级的过程
synchronized 锁升级机制也叫做锁膨胀机制,此机制诞生于 JDK 6 中。在 Java 6 及之前的版本中,synchronized 的实现主要依赖于操作系统的 mutex 锁(重量级锁),而在 Java 6 及之后的版本中,Java 对 synchronized 进行了升级,引入了锁升级的机制,可以更加高效地利用 CPU 的多级缓存,提升了多线程并发性能。
synchronized 锁升级的过程可以分为以下四个阶段:无锁状态、偏向锁、轻量级锁和重量级锁。其中,无锁状态和偏向锁状态都属于乐观锁,不需要进行锁升级,锁竞争较少,能够提高程序的性能。只有在锁竞争激烈的情况下,才会进行锁升级,将锁升级为轻量级锁状态。
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁;
1.无锁状态
当前无任何线程获取它,所以此过程不需要加锁,是无锁状态。当一个线程访问一个同步块时,如果该同步块没有被其他线程占用,那么该线程就可以直接进入同步块,并且将同步块标记为偏向锁状态。
2.偏向锁状态
在偏向锁状态下,同步块已经被一个线程占用,其他线程访问该同步块时,只需要判断该同步块是否被当前线程占用,如果是,则直接进入同步块。这个过程不需要进行任何加锁操作,仍然属于乐观锁状态。
3.轻量级锁状态
如果在偏向锁状态下,有多个线程竞争同一个同步块,那么该同步块就会升级为轻量级锁状态。此时,每个线程都会在自己的 CPU 缓存中保存该同步块的副本,并通过 CAS(Compare and Swap)操作来对同步块进行加锁和解锁。这个过程需要进行加锁操作,但相对于传统的 mutex 锁,轻量级锁的效率要高很多。
4.重量级锁状态
轻量级锁之后会通过自旋来获取锁,自旋执行一定次数之后还未成功获取到锁,此时就会升级为重量级锁,并且进入阻塞状态。
synchronized 锁升级的过程可以有效地减少锁竞争,提高多线程并发性。
更多推荐




所有评论(0)