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方法时,他们的执行也是按顺序来的,也就是说一个线程先执行,一个线程后执行。

 

 

posted on 2013-10-08 15:59  gogoy  阅读(464)  评论(0编辑  收藏  举报

导航