Java

【Java并发编程实战】-----synchronized

在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者、写相同的数据,访问相同的文件等等。对于这种情况如果我们不加以控制,是非常容易导致错误的。在java中,为了解决这个问题,引入临界区概念。所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问。

在java中为了实现临界区提供了同步机制。当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区。如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区。

临界区规定:每次只准许一个进程进入临界区,进入后不允许其他进程进入。调度法则为(百度百科):

1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。

2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。

3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

下面介绍使用synchronized关键字来实现同步机制。

一、synchronized关键字

1.1、简介

synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。

synchronized主要包括两种方法:synchronized 方法、synchronized 块。

1.2、synchronized 方法

通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void getResult();

synchronized方法控制对类成员变量的访问。它是如何来避免类成员变量的访问控制呢?我们知道方法使用了synchronized关键字表明该方法已加锁,在任一线程在访问改方法时都必须要判断该方法是否有其他线程在“独占”。每个类实例对应一个把锁,每个synchronized方法都必须调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,被阻塞的线程方能获得该锁。

其实synchronized方法是存在缺陷的,如果我们将一个很大的方法声明为synchronized将会大大影响效率的。如果多个线程在访问一个synchronized方法,那么同一时刻只有一个线程在执行该方法,而其他线程都必须等待,但是如果该方法没有使用synchronized,则所有线程可以在同一时刻执行它,减少了执行的总时间。所以如果我们知道一个方法不会被多个线程执行到或者说不存在资源共享的问题,则不需要使用synchronized关键字。但是如果一定要使用synchronized关键字,那么我们可以synchronized代码块来替换synchronized方法。

1.3、synchronized 块

synchronized代码块所起到的作用和synchronized方法一样,只不过它使临界区变的尽可能短了,换句话说:它只把需要的共享数据保护起来,其余的长代码块留出此操作。语法如下:

synchronized(object) {  
    //允许访问控制的代码  
}

如果我们需要以这种方式来使用synchronized关键字,那么必须要通过一个对象引用来作为参数,通常这个参数我们常使用为this.

synchronized (this) {
    //允许访问控制的代码 
}

对于synchronized(this)有如下理解:

1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问object中的非synchronized(this)同步代码块。

3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其他synchronized(this)同步代码块得访问将被阻塞。

4、第三个例子同样适用其他同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其他线程对该object对象所有同步代码部分的访问都将被暂时阻塞。

5、以上规则对其他对象锁同样适用

http://freewxy.iteye.com/blog/978159,这篇博客使用实例对上面四点进行了较为详细的说明,这里就不多阐述了。

http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html这篇博客对synchronized的使用举了一个很不错的例子(拿钥匙进房间)。这里由于篇幅问题LZ就不多阐述了,下面我们来刨刨synchronized稍微高级点的东西。

我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

第一条

当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
下面是“synchronized代码块”对应的演示程序。

复制代码
 1 class MyRunable implements Runnable {
 2     
 3     @Override
 4     public void run() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 }
16 
17 public class Demo1_1 {
18 
19     public static void main(String[] args) {  
20         Runnable demo = new MyRunable();     // 新建“Runnable对象”
21 
22         Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
23         Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
24         t1.start();                          // 启动“线程t1”
25         t2.start();                          // 启动“线程t2” 
26     } 
27 }
复制代码

运行结果

复制代码
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
复制代码

结果说明
run()方法中存在“synchronized(this)代码块”,而且t1和t2都是基于"demo这个Runnable对象"创建的线程。这就意味着,我们可以将synchronized(this)中的this看作是“demo这个Runnable对象”;因此,线程t1和t2共享“demo对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待“运行线程”释放“demo的同步锁”之后才能运行。

如果你确认,你搞清楚这个问题了。那我们将上面的代码进行修改,然后再运行看看结果怎么样,看看你是否会迷糊。修改后的源码如下:

复制代码
 1 class MyThread extends Thread {
 2     
 3     public MyThread(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void run() {
 9         synchronized(this) {
10             try {  
11                 for (int i = 0; i < 5; i++) {
12                     Thread.sleep(100); // 休眠100ms
13                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
14                 }
15             } catch (InterruptedException ie) {  
16             }
17         }  
18     }
19 }
20 
21 public class Demo1_2 {
22 
23     public static void main(String[] args) {  
24         Thread t1 = new MyThread("t1");  // 新建“线程t1”
25         Thread t2 = new MyThread("t2");  // 新建“线程t2”
26         t1.start();                          // 启动“线程t1”
27         t2.start();                          // 启动“线程t2” 
28     } 
29 }
复制代码

代码说明
比较Demo1_2 和 Demo1_1,我们发现,Demo1_2中的MyThread类是直接继承于Thread,而且t1和t2都是MyThread子线程。
幸运的是,在“Demo1_2的run()方法”也调用了synchronized(this),正如“Demo1_1的run()方法”也调用了synchronized(this)一样!
那么,Demo1_2的执行流程是不是和Demo1_1一样呢?
运行结果:

复制代码
t1 loop 0
t2 loop 0
t1 loop 1
t2 loop 1
t1 loop 2
t2 loop 2
t1 loop 3
t2 loop 3
t1 loop 4
t2 loop 4
复制代码

结果说明
如果这个结果一点也不令你感到惊讶,那么我相信你对synchronized和this的认识已经比较深刻了。否则的话,请继续阅读这里的分析。
synchronized(this)中的this是指“当前的类对象”,即synchronized(this)所在的类对应的当前对象。它的作用是获取“当前对象的同步锁”。
对于Demo1_2中,synchronized(this)中的this代表的是MyThread对象,而t1和t2是两个不同的MyThread对象,因此t1和t2在执行synchronized(this)时,获取的是不同对象的同步锁。对于Demo1_1对而言,synchronized(this)中的this代表的是MyRunable对象;t1和t2共同一个MyRunable对象,因此,一个线程获取了对象的同步锁,会造成另外一个线程等待。

第二条

当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
下面是“synchronized代码块”对应的演示程序。

复制代码
 1 class Count {
 2 
 3     // 含有synchronized同步块的方法
 4     public void synMethod() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 
16     // 非同步的方法
17     public void nonSynMethod() {
18         try {  
19             for (int i = 0; i < 5; i++) {
20                 Thread.sleep(100);
21                 System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
22             }
23         } catch (InterruptedException ie) {  
24         }
25     }
26 }
27 
28 public class Demo2 {
29 
30     public static void main(String[] args) {  
31         final Count count = new Count();
32         // 新建t1, t1会调用“count对象”的synMethod()方法
33         Thread t1 = new Thread(
34                 new Runnable() {
35                     @Override
36                     public void run() {
37                         count.synMethod();
38                     }
39                 }, "t1");
40 
41         // 新建t2, t2会调用“count对象”的nonSynMethod()方法
42         Thread t2 = new Thread(
43                 new Runnable() {
44                     @Override
45                     public void run() {
46                         count.nonSynMethod();
47                     }
48                 }, "t2");  
49 
50 
51         t1.start();  // 启动t1
52         t2.start();  // 启动t2
53     } 
54 }
复制代码

运行结果

复制代码
t1 synMethod loop 0
t2 nonSynMethod loop 0
t1 synMethod loop 1
t2 nonSynMethod loop 1
t1 synMethod loop 2
t2 nonSynMethod loop 2
t1 synMethod loop 3
t2 nonSynMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 4
复制代码

结果说明
主线程中新建了两个子线程t1和t2。t1会调用count对象的synMethod()方法,该方法内含有同步块;而t2则会调用count对象的nonSynMethod()方法,该方法不是同步方法。t1运行时,虽然调用synchronized(this)获取“count的同步锁”;但是并没有造成t2的阻塞,因为t2没有用到“count”同步锁。

第三条

当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
我们将上面的例子中的nonSynMethod()方法体的也用synchronized(this)修饰。修改后的源码如下:

复制代码
 1 class Count {
 2 
 3     // 含有synchronized同步块的方法
 4     public void synMethod() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 
16     // 也包含synchronized同步块的方法
17     public void nonSynMethod() {
18         synchronized(this) {
19             try {  
20                 for (int i = 0; i < 5; i++) {
21                     Thread.sleep(100);
22                     System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
23                 }
24             } catch (InterruptedException ie) {  
25             }
26         }
27     }
28 }
29 
30 public class Demo3 {
31 
32     public static void main(String[] args) {  
33         final Count count = new Count();
34         // 新建t1, t1会调用“count对象”的synMethod()方法
35         Thread t1 = new Thread(
36                 new Runnable() {
37                     @Override
38                     public void run() {
39                         count.synMethod();
40                     }
41                 }, "t1");
42 
43         // 新建t2, t2会调用“count对象”的nonSynMethod()方法
44         Thread t2 = new Thread(
45                 new Runnable() {
46                     @Override
47                     public void run() {
48                         count.nonSynMethod();
49                     }
50                 }, "t2");  
51 
52 
53         t1.start();  // 启动t1
54         t2.start();  // 启动t2
55     } 
56 }
复制代码

运行结果

复制代码
t1 synMethod loop 0
t1 synMethod loop 1
t1 synMethod loop 2
t1 synMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 0
t2 nonSynMethod loop 1
t2 nonSynMethod loop 2
t2 nonSynMethod loop 3
t2 nonSynMethod loop 4
复制代码

结果说明
主线程中新建了两个子线程t1和t2。t1和t2运行时都调用synchronized(this),这个this是Count对象(count),而t1和t2共用count。因此,在t1运行时,t2会被阻塞,等待t1运行释放“count对象的同步锁”,t2才能运行。

4. 实例锁 和 全局锁

实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
               实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
               全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

关于“实例锁”和“全局锁”有一个很形象的例子:

pulbic class Something {
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB() 
(02) x.isSyncA()与y.isSyncA()
(03) x.cSyncA()与y.cSyncB()
(04) x.isSyncA()与Something.cSyncA()

(01) 不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!

复制代码
 1 // LockTest1.java的源码
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21 }
22 
23 public class LockTest1 {
24 
25     Something x = new Something();
26     Something y = new Something();
27 
28     // 比较(01) x.isSyncA()与x.isSyncB() 
29     private void test1() {
30         // 新建t11, t11会调用 x.isSyncA()
31         Thread t11 = new Thread(
32                 new Runnable() {
33                     @Override
34                     public void run() {
35                         x.isSyncA();
36                     }
37                 }, "t11");
38 
39         // 新建t12, t12会调用 x.isSyncB()
40         Thread t12 = new Thread(
41                 new Runnable() {
42                     @Override
43                     public void run() {
44                         x.isSyncB();
45                     }
46                 }, "t12");  
47 
48 
49         t11.start();  // 启动t11
50         t12.start();  // 启动t12
51     }
52 
53     public static void main(String[] args) {
54         LockTest1 demo = new LockTest1();
55         demo.test1();
56     }
57 }
复制代码

运行结果

复制代码
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
复制代码

(02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

复制代码
 1 // LockTest2.java的源码
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest2 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比较(02) x.isSyncA()与y.isSyncA()
47     private void test2() {
48         // 新建t21, t21会调用 x.isSyncA()
49         Thread t21 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.isSyncA();
54                     }
55                 }, "t21");
56 
57         // 新建t22, t22会调用 x.isSyncB()
58         Thread t22 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         y.isSyncA();
63                     }
64                 }, "t22");  
65 
66 
67         t21.start();  // 启动t21
68         t22.start();  // 启动t22
69     }
70 
71     public static void main(String[] args) {
72         LockTest2 demo = new LockTest2();
73 
74         demo.test2();
75     }
76 }
复制代码

运行结果

复制代码
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
复制代码

(03) 不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。

复制代码
 1 // LockTest3.java的源码
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest3 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比较(03) x.cSyncA()与y.cSyncB()
47     private void test3() {
48         // 新建t31, t31会调用 x.isSyncA()
49         Thread t31 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.cSyncA();
54                     }
55                 }, "t31");
56 
57         // 新建t32, t32会调用 x.isSyncB()
58         Thread t32 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         y.cSyncB();
63                     }
64                 }, "t32");  
65 
66 
67         t31.start();  // 启动t31
68         t32.start();  // 启动t32
69     }
70 
71     public static void main(String[] args) {
72         LockTest3 demo = new LockTest3();
73 
74         demo.test3();
75     }
76 }
复制代码

运行结果

复制代码
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
复制代码

(04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

复制代码
 1 // LockTest4.java的源码
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest4 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比较(04) x.isSyncA()与Something.cSyncA()
47     private void test4() {
48         // 新建t41, t41会调用 x.isSyncA()
49         Thread t41 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.isSyncA();
54                     }
55                 }, "t41");
56 
57         // 新建t42, t42会调用 x.isSyncB()
58         Thread t42 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         Something.cSyncA();
63                     }
64                 }, "t42");  
65 
66 
67         t41.start();  // 启动t41
68         t42.start();  // 启动t42
69     }
70 
71     public static void main(String[] args) {
72         LockTest4 demo = new LockTest4();
73 
74         demo.test4();
75     }
76 }
复制代码

运行结果

复制代码
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA

1.4、进阶

在java多线程中存在一个“先来后到”的原则,也就是说谁先抢到钥匙,谁先用。我们知道为避免资源竞争产生问题,java使用同步机制来避免,而同步机制是使用锁概念来控制的。那么在Java程序当中,锁是如何体现的呢?这里我们需要弄清楚两个概念:

什么是锁?

什么是锁?在日常生活中,它就是一个加在门、箱子、抽屉等物体上的封缄器,防止别人偷窥或者偷盗,起到一个保护的作用。在java中同样如此,锁对对象起到一个保护的作用,一个线程如果独占了某个资源,那么其他的线程别想用,想用?等我用完再说吧!

在java程序运行环境中,JVM需要对两类线程共享的数据进行协调:

1、保存在堆中的实例变量

2、保存在方法区中的类变量。

在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。对于对象来说,相关联的监视器保护对象的实例变量。 对于类来说,监视器保护类的类变量。如果一个对象没有实例变量,或者说一个类没有变量,相关联的监视器就什么也不监视。

为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。 如果某个线程获取了锁,那么在它释放该锁之前其他线程是不可能获取同样锁的。一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。 
java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。(摘自java的锁机制)。

锁的是什么?

在这个问题之前我们必须要明确一点:无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象。在java中每一个对象都可以作为锁,它主要体现在下面三个方面:

  • 对于同步方法,锁是当前实例对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。
  • 对于静态同步方法,锁是当前对象的Class对象。

    首先我们先看下面例子:

    复制代码
    public class ThreadTest_01 implements Runnable{
    
        @Override
        public synchronized void run() {
            for(int i = 0 ; i < 3 ; i++){
                System.out.println(Thread.currentThread().getName() + "run......");
            }
        }
        
        public static void main(String[] args) {
            for(int i = 0 ; i < 5 ; i++){
                new Thread(new ThreadTest_01(),"Thread_" + i).start();
            }
        }
    }
    复制代码

    部分运行结果:

    复制代码
    Thread_2run......
    Thread_2run......
    Thread_4run......
    Thread_4run......
    Thread_3run......
    Thread_3run......
    Thread_3run......
    Thread_2run......
    Thread_4run......
    复制代码

    这个结果与我们预期的结果有点不同(这些线程在这里乱跑),照理来说,run方法加上synchronized关键字后,会产生同步效果,这些线程应该是一个接着一个执行run方法的。在上面LZ提到,一个成员方法加上synchronized关键字后,实际上就是给这个成员方法加上锁,具体点就是以这个成员方法所在的对象本身作为对象锁。但是在这个实例当中我们一共new了10个ThreadTest对象,那个每个线程都会持有自己线程对象的对象锁,这必定不能产生同步的效果。所以:如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!

    这个时候synchronized锁住的是那个对象?它锁住的就是调用这个同步方法对象。就是说threadTest这个对象在不同线程中执行同步方法,就会形成互斥。达到同步的效果。所以将上面的new Thread(new ThreadTest_01(),"Thread_" + i).start(); 修改为newThread(threadTest,"Thread_" + i).start();就可以了。

    对于同步方法,锁是当前实例对象。

    上面实例是使用synchronized方法,我们在看看synchronized代码块:

    复制代码
    public class ThreadTest_02 extends Thread{
    
        private String lock ;
        private String name;
        
        public ThreadTest_02(String name,String lock){
            this.name = name;
            this.lock = lock;
        }
        
        @Override
        public void run() {
            synchronized (lock) {
                for(int i = 0 ; i < 3 ; i++){
                    System.out.println(name + " run......");
                }
            }
        }
        
        public static void main(String[] args) {
            String lock  = new String("test");
            for(int i = 0 ; i < 5 ; i++){
                new ThreadTest_02("ThreadTest_" + i,lock).start();
            }
        }
    }
    复制代码

    运行结果:

    复制代码
    ThreadTest_0 run......
    ThreadTest_0 run......
    ThreadTest_0 run......
    ThreadTest_1 run......
    ThreadTest_1 run......
    ThreadTest_1 run......
    ThreadTest_4 run......
    ThreadTest_4 run......
    ThreadTest_4 run......
    ThreadTest_3 run......
    ThreadTest_3 run......
    ThreadTest_3 run......
    ThreadTest_2 run......
    ThreadTest_2 run......
    ThreadTest_2 run......
    复制代码

    在main方法中我们创建了一个String对象lock,并将这个对象赋予每一个ThreadTest2线程对象的私有变量lock。我们知道java中存在一个字符串池,那么这些线程的lock私有变量实际上指向的是堆内存中的同一个区域,即存放main函数中的lock变量的区域,所以对象锁是唯一且共享的。线程同步!!

    在这里synchronized锁住的就是lock这个String对象。

    对于同步方法块,锁是Synchonized括号里配置的对象。

    复制代码
    public class ThreadTest_03 extends Thread{
    
        public synchronized static void test(){
            for(int i = 0 ; i < 3 ; i++){
                System.out.println(Thread.currentThread().getName() + " run......");
            }
        }
        
        @Override
        public void run() {
            test();
        }
    
        public static void main(String[] args) {
            for(int i = 0 ; i < 5 ; i++){
                new ThreadTest_03().start();
            }
        }
    }
    复制代码

    运行结果:

    复制代码
    Thread-0 run......
    Thread-0 run......
    Thread-0 run......
    Thread-4 run......
    Thread-4 run......
    Thread-4 run......
    Thread-1 run......
    Thread-1 run......
    Thread-1 run......
    Thread-2 run......
    Thread-2 run......
    Thread-2 run......
    Thread-3 run......
    Thread-3 run......
    Thread-3 run......
    复制代码

    在这个实例中,run方法使用的是一个同步方法,而且是static的同步方法,那么这里synchronized锁的又是什么呢?我们知道static超脱于对象之外,它属于类级别的。所以,对象锁就是该静态放发所在的类的Class实例。由于在JVM中,所有被加载的类都有唯一的类对象,在该实例当中就是唯一的 ThreadTest_03.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!所以对象锁是唯一且共享的。线程同步!!

    对于静态同步方法,锁是当前对象的Class对象。

    如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized的instance函数B,那么这个类的同一对象Obj,在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

    锁的升级

    java中锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。下面主要部分主要是对博客:聊聊并发(二)Java SE1.6中的Synchronized的总结。

    锁自旋

    我们知道在当某个线程在进入同步方法/代码块时若发现该同步方法/代码块被其他现在所占,则它就要等待,进入阻塞状态,这个过程性能是低下的。

    在遇到锁的争用或许等待事,线程可以不那么着急进入阻塞状态,而是等一等,看看锁是不是马上就释放了,这就是锁自旋。锁自旋在一定程度上可以对线程进行优化处理。

    偏向锁

    偏向锁主要为了解决在没有竞争情况下锁的性能问题。在大多数情况下锁锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当某个线程获得锁的情况,该线程是可以多次锁住该对象,但是每次执行这样的操作都会因为CAS(CPU的Compare-And-Swap指令)操作而造成一些开销消耗性能,为了减少这种开销,这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

    当有其他线程在尝试着竞争偏向锁时,持有偏向锁的线程就会释放锁。

    锁膨胀

    多个或多次调用粒度太小的锁,进行加锁解锁的消耗,反而还不如一次大粒度的锁调用来得高效。

    轻量级锁

    轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。轻量级锁在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的指向和状态。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

原文地址:https://www.cnblogs.com/qlky/p/7368068.html