☰
synchronized锁的到底是谁?
2026/11/6 1:59:55 网站建设 项目流程

synchronized锁的到底是谁?

首先来看一个例子

public class Test { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test.test(); } }).start(); } } public static void test(){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } }

运行结果

Thread-3开始 Thread-1开始 Thread-0开始 Thread-2开始 Thread-4开始 Thread-1结束 Thread-3结束 Thread-0结束 Thread-2结束 Thread-4结束

五个开始,之后五个结束,如何确保一下顺序,每个线程结束之后才能开启下一个线程执行。
用synchronized修饰方法

public class Test { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test.test(); } }).start(); } } public static synchronized void test(){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } }

运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-1开始 Thread-1结束 Thread-2开始 Thread-2结束

确实是每个线程执行完之后才能让下个线程开始执行,再看下一个例子。
synchronized修饰的是普通方法而不是静态方法

public class Test { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public synchronized void test(){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } }

运行结果

Thread-0开始 Thread-2开始 Thread-1开始 Thread-4开始 Thread-3开始 Thread-0结束 Thread-4结束 Thread-2结束 Thread-3结束 Thread-1结束

情况出现了,修饰静态方法可以保证线程同步,而修饰普通方法不能保证线程同步。(仅限于上面出现的情况)
原因:synchronized修饰静态方法的时候,锁的是类,修饰普通方法时,锁的是实例对象。
在修饰普通方法的时候,因为每个线程锁的是自己的实例对象,也就是new的那个Test,每个线程互不干扰。
所以,synchronized修饰普通方法,锁的是调用这个方法的实例对象。修饰静态方法时,锁的是类Class,Class对象的唯一性依赖类加载器,同一 ClassLoader下唯一;跨ClassLoader会有多份,锁随之失效。
如何解决上面修饰普通方法导致的线程安全问题?同步代码块。

public class Test { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(Test.class){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-1开始 Thread-1结束 Thread-2开始 Thread-2结束

上面同步代码块锁的是类对象,能不能锁别的?

public class Test { Object object = new Object(); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(object){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-1开始 Thread-2开始 Thread-4开始 Thread-0开始 Thread-3开始 Thread-1结束 Thread-0结束 Thread-2结束 Thread-4结束 Thread-3结束

分析:每次循环new线程执行任务的时候,都new了Test,也就是new了5个Object,每个线程锁的是自己的Object对象,互不干扰。
怎么解决这个问题?
每次循环都new了个Test,导致每次new了Object,一个解决方法是把Test从循环里拿出去,这样Test只new一次,Object也只有一个。

public class Test { Object object = new Object(); public static void main(String[] args) { Test test = new Test(); for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { // Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(object){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-2开始 Thread-2结束 Thread-1开始 Thread-1结束

还可以将Object修饰为静态的,这样全局只有一份。

public class Test { static Object object = new Object(); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(object){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-0结束 Thread-3开始 Thread-3结束 Thread-4开始 Thread-4结束 Thread-2开始 Thread-2结束 Thread-1开始 Thread-1结束

从上面同步代码块进行分析:
锁的是Class对象的时候,可以同步;
每次new Test之后,导致又new了Object,锁Object,同步失败;
把new Test放在循环外面,Test实例只有一个,导致Object实例也只有一个,锁Object,可以同步;
每次new Test之后,由于Object被修饰为静态的,全局只有一份,可以同步
发现,能同步的三种情况,共同点不是"Object都只有一个",而是各线程锁的那个对象是同一个:
第一种情况锁的是Test.class,Class对象是同一个;
第三种情况锁的是同一个Test实例里的同一个object,是同一个;
第四种情况锁的是静态的object,也是同一个。
而第二种情况,每个线程锁的是自己new出来的Test里的object,5个不同的对象,所以同步失败。
所以结论是:synchronized同步代码块,锁的是括号里的对象;同步能否生效,只看各线程锁的这个对象是不是同一个,跟"有几个Object变量"没有关系

再来看个例子,把上面new Object换为Integer

public class Test { Integer num = Integer.valueOf("1"); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(num){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

如果还按照上面"new一个Test就new一个Object"的思路去思考的话,是不会实现线程同步的,但是运行情况是实现了线程同步
运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-1开始 Thread-1结束 Thread-2开始 Thread-2结束

原因是Integer在-128到127之间有缓存,Integer.valueOf("1")每一次调用都直接返回缓存池里的同一个Integer对象,所以5个线程锁的都是同一个对象。
把数换为128再看

public class Test { Integer num = Integer.valueOf("128"); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(num){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-3开始 Thread-1开始 Thread-2开始 Thread-0开始 Thread-4开始 Thread-0结束 Thread-1结束 Thread-3结束 Thread-2结束 Thread-4结束

这次每次new Test的时候,128不在IntegerCache里,每次都会创建新的Integer对象,线程同步失败。

看到这里,应该想到了锁字符串
如果锁字符串

public class Test { String str = "java"; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(str){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-2开始 Thread-2结束 Thread-1开始 Thread-1结束
public class Test { String str = new String("java"); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized(str){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-4开始 Thread-2开始 Thread-1开始 Thread-3开始 Thread-0结束 Thread-4结束 Thread-3结束 Thread-1结束 Thread-2结束

也都出现了同步成功和同步失败,这是因为字符串常量池。"java"是常量池全局唯一对象,任何地方只要锁"java",就会和其他锁"java"的线程抢同一把锁,造成阻塞,实际上禁止用字符串,包装类当锁。

上面锁的都是引用数据类型,如果锁基本数据类型了?synchronized只能锁引用数据类型,因为基本类型没有对象头、没有监视器(Monitor),不能当锁。下面用(Object)num这种写法,它会触发自动装箱。

public class Test { int num = 1; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized((Object)num){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-0开始 Thread-0结束 Thread-4开始 Thread-4结束 Thread-3开始 Thread-3结束 Thread-2开始 Thread-2结束 Thread-1开始 Thread-1结束

改num为128

public class Test { int num = 128; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { Test test = new Test(); test.test(); } }).start(); } } public void test(){ synchronized((Object)num){ System.out.println(Thread.currentThread().getName() +"开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "结束"); } } }

运行结果

Thread-3开始 Thread-0开始 Thread-4开始 Thread-1开始 Thread-2开始 Thread-0结束 Thread-3结束 Thread-2结束 Thread-1结束 Thread-4结束

从上面结果和Integer对比,可以知道,虽然锁的表面上是int,但(Object)num并不是简单地把int强转成引用,而是触发了自动装箱,编译后等价于Integer.valueOf(num),所以表现和上面Integer完全一致。

通过上面内容进行总结synchronized锁的是谁
同步方法:修饰普通方法,锁的是调用这个方法的实例对象;修饰静态方法,锁的是这个Class对象
同步代码块:锁的是括号里的对象。

判断同步是否生效,只看一件事:各线程锁的那个对象是不是同一个,跟变量本身是不是静态没有必然关系。
锁 new Object() 这种实例变量:每个实例一份,各自new实例就锁不住,必须共用实例或加static。
锁 static 变量:全局一份,可以同步(一个JVM实例、同一个ClassLoader情况下)。
锁 Integer.valueOf(1) 这种非静态变量:变量虽然每个实例一份,但装箱后都指向缓存池里的同一个对象,所以照样能同步,这就是"变量不唯一但锁对象唯一"的例外。
锁字符串常量、包装类缓存:能同步,但容易和无关代码抢同一把锁,禁止使用。
锁基本类型:(Object)num 是自动装箱,本质还是锁 Integer 缓存对象,num超出-128~127就同步失效

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询