JAVA多线程(2)——锁(对象锁和类锁)
1、如下代码
1 public class TestSync1 implements Runnable { 2 Timer1 timer = new Timer1(); 3 public static void main(String[] args) { 4 TestSync1 test = new TestSync1(); 5 Thread t1 = new Thread(test); 6 Thread t2 = new Thread(test); 7 t1.setName("t1"); 8 t2.setName("t2"); 9 t1.start(); 10 t2.start(); 11 } 12 13 public void run() { 14 timer.add(Thread.currentThread().getName()); 15 } 16 17 } 18 19 class Timer1 { 20 private static int num = 0; 21 //int num = 0; 22 public void add(String name) { 23 //synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了 24 num++; //这一段的执行过程被打断了 25 try { 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 System.out.println(name + ", 你是第" + num + "个使用timer的线程"); 31 //} 32 }
结果:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
2、解决:加锁
1 public class TestSync1 implements Runnable { 2 Timer1 timer = new Timer1(); 3 public static void main(String[] args) { 4 TestSync1 test = new TestSync1(); 5 Thread t1 = new Thread(test); 6 Thread t2 = new Thread(test); 7 t1.setName("t1"); 8 t2.setName("t2"); 9 t1.start(); 10 t2.start(); 11 } 12 13 public void run() { 14 timer.add(Thread.currentThread().getName()); 15 } 16 17 } 18 19 class Timer1 { 20 private static int num = 0; 21 //int num = 0; 22 public void add(String name) { 23 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了 24 num++; 25 try { 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 System.out.println(name + ", 你是第" + num + "个使用timer的线程"); 31 } 32 }
结果:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t2, 你是第1个使用timer的线程
t1, 你是第2个使用timer的线程
分析一下1、2中的内存情况:只有一个test实例,该实例包含一个timer实例变量,因此代码中的private static int num = 0;换成int num = 0;对结果没有任何影响
接着,我们考虑如下这段代码,即在上述例子基础上新增了一个test实例,即共有test1、test2两个实例:
1 public class TestSync1 implements Runnable { 2 Timer1 timer = new Timer1(); 3 public static void main(String[] args) { 4 TestSync1 test1 = new TestSync1(); 5 TestSync1 test2 = new TestSync1(); 6 Thread t1 = new Thread(test1); 7 Thread t2 = new Thread(test2); 8 9 t1.setName("t1"); 10 t2.setName("t2"); 11 t1.start(); 12 t2.start(); 13 } 14 15 public void run() { 16 timer.add(Thread.currentThread().getName()); 17 } 18 19 } 20 21 class Timer1 { 22 //private static int num = 0; 23 int num = 0; 24 public void add(String name) { 25 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了 26 num++; //这一段的执行过程被打断了 27 try { 28 Thread.sleep(3000); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 System.out.println(name + ", 你是第" + num + "个使用timer的线程"); 33 } 34 }
结果:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t1, 你是第1个使用timer的线程
t2, 你是第1个使用timer的线程
分析一下内存使用情况:有两个test实例:test1、test2,它们分别各自指向自己的实例变量timer
如果第23行改为,private static int num = 0;,则执行结果为
D:\聚划算\技术部\编程练习\TestThread\Sync>java Test
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
这是因为此时num为Timer的类变量,为所有实例所共有
简单改造一下,把Timer1 这个类相关的方法执行放到run方法里面,如下:
1 public class TestSyncNew implements Runnable { 2 int num = 0; 3 4 public static void main(String[] args) { 5 TestSyncNew test1 = new TestSyncNew(); 6 //TestSyncNew test2 = new TestSyncNew(); 7 Thread t1 = new Thread(test1); 8 Thread t2 = new Thread(test1); 9 //Thread t2 = new Thread(test2); 10 11 t1.setName("t1"); 12 t2.setName("t2"); 13 t1.start(); 14 t2.start(); 15 } 16 17 public void run() { 18 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了 19 num++; //这一段的执行过程被打断了 20 try { 21 Thread.sleep(1000); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 System.out.println(Thread.currentThread().getName() + ", 你是第" + num + "个使用timer的线程"); 26 } 27 } 28 }
结果为:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSyncNew
t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程
1 public class TestSyncNew implements Runnable { 2 int num = 0; 3 4 public static void main(String[] args) { 5 TestSyncNew test1 = new TestSyncNew(); 6 TestSyncNew test2 = new TestSyncNew(); 7 Thread t1 = new Thread(test1); 8 //Thread t2 = new Thread(test1); 9 Thread t2 = new Thread(test2); 10 11 t1.setName("t1"); 12 t2.setName("t2"); 13 t1.start(); 14 t2.start(); 15 } 16 17 public void run() { 18 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了 19 num++; //这一段的执行过程被打断了 20 try { 21 Thread.sleep(1000); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 System.out.println(Thread.currentThread().getName() + ", 你是第" + num + "个使用timer的线程"); 26 } 27 } 28 }
结果为:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSyncNew
t1, 你是第1个使用timer的线程
t2, 你是第1个使用timer的线程
如上的两个例子内存空间分别如下的左右两边所示,例子1
解释结果:参考了http://jasshine.iteye.com/blog/1617813
例子1:
因为加了synchronzied,实现了同步,并且该对象锁对应的对象只有一个,那就是test1,所以当第一个线程锁住了test1,而第二个线程里面也是通过test1去访问run()方法,所以必须等第一个线程执行完对象的方法时才能获得对象锁。因此必须隔1秒钟才能执行当前线程
例子2:
因为此时每个线程都是通过不同的对象去访问run()方法,一个为test1,另外一个为test2,所以有2把对象锁,这2个对象锁毫不干,第一个线程锁住了test1,而第二个线程都是通过
test2对象去访问的,所以仍然能访问该方法。
类锁:
1 class TestSynchronized extends Thread { 2 public TestSynchronized(String name) { 3 super(name); 4 } 5 6 public synchronized static void prt() { 7 for (int i = 10; i < 20; i++) { 8 System.out.println(Thread.currentThread().getName() + " : " + i); 9 try { 10 Thread.sleep(100); 11 } catch (InterruptedException e) { 12 System.out.println("Interrupted"); 13 } 14 } 15 } 16 17 public synchronized void run() { 18 System.out.println(Thread.currentThread().getName() + " in here"); 19 /* 20 for (int i = 0; i < 10; i++) { 21 System.out.println(Thread.currentThread().getName() + " : " + i); 22 try { 23 Thread.sleep(100); 24 } catch (InterruptedException e) { 25 System.out.println("Interrupted"); 26 } 27 } */ 28 } 29 } 30 31 public class TestThread { 32 public static void main(String[] args) { 33 TestSynchronized t1 = new TestSynchronized("t1"); 34 TestSynchronized t2 = new TestSynchronized("t2"); 35 t1.start(); 36 37 t1.prt();// (1) 38 t2.prt();// (2) 39 40 } 41 }
在代码(1)中,虽然是通过对象t1来调用prt()函数的,但由于prt()是静态的,所以调用它时不用经过任何对象,它所属的线程为main线程。
由于调用run()函数取走的是对象锁,而调用prt()函数取走的是class锁,所以同一个线程t1(由上面可知实际上是不同线程)调用run()函数且还没完成run()函数时,它就能调用prt()函数。但prt()函数只能被一个线程调用,如代码(1)和代码(2),即使是两个不同的对象也不能同时调用prt()。
结果:
D:\聚划算\技术部\编程练习\TestThread\Sync>java TestThread
main : 10
t1 in here //如果注释掉35行 t1.start();,则该行不打印
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19
main : 10
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19
上面的一个程序足以说明同步方法、对象锁和类锁的概念了。总结一下:
1. java中的每个对象都有一个锁,当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法在去访问该syncronized 方法了,直到之前的那个线程执行方法完毕后,其他线程才有可能去访问该synchronized方法。
2.如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronzed方法,那么在该方法没有执行完毕前,其他线程无法访问该对象的任何synchronzied 方法的,但可以访问非synchronzied方法。
3.如果synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchuronized方法所在对象的对应的Class对象(类锁),
因为java中无论一个类有多少个对象,这些对象会对应唯一一个Class 对象,因此当线程分别访问同一个类的两个对象的static,synchronized方法时,他们的执行也是按顺序来的,也就是说一个线程先执行,一个线程后执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏