Java并发编程基础——同步

一、synchronized 关键字

  1)synchronized 锁什么?锁对象。可能锁对象包括: this, 临界资源对象,Class 类对象。如同下面例子所示;

 1 package cn.test.juc;
 2 
 3 public class TestSynchronized {
 4 
 5     private int count = 0;
 6     private Object object = new Object();
 7 
 8 
 9     public void testSyn1() {
10         //锁对象(这里面是锁临界资源)
11         synchronized (object) {
12             System.out.println(Thread.currentThread().getName()
13                 +" count =" + count++);
14         }
15     }
16 
17     public void testSyn2() {
18         //锁当前对象
19         synchronized (this) {
20             System.out.println(Thread.currentThread().getName()
21                     +" count =" + count++);
22         }
23     }
24 
25     //锁当前对象
26     public synchronized void testSyn3() {
27         System.out.println(Thread.currentThread().getName()
28                 +" count =" + count++);
29     }
30 }

  2)如果在加锁的时候对当前对象的访问限定要求比较低的时候,建议锁某一段代码或者某一个对象;如果访问限定要求比较高的话,建议锁当前对象。简单而言就可以说是减小锁的范围。对于锁当前对象或者都是重量级锁,什么意思呢,“就是任意多个线程,多个资源不会被多个线程访问所影响的”

  3)再看下面的例子,锁当前类的类对象的两种方式:

 1 public class TestSynchronized02 {
 2     private static int staticCount = 0;
 3 
 4     //静态同步方法,锁的是当前类型的类对象(即TestSynchronized02.class)
 5     public static synchronized void testSyn1() {
 6         System.out.println(Thread.currentThread().getName()
 7                 +" staticCount =" + staticCount++);
 8     }
 9 
10     //下面的这种方式也是锁当前类型的类对象
11     public static void testSyn2() {
12         synchronized (TestSynchronized02.class) {
13             System.out.println(Thread.currentThread().getName()
14                     +" staticCount =" + staticCount++);
15         }
16     }
17 }

  4)看一下下面一段小程序的运行结果

 1 public class TestSynchronized03 implements Runnable{
 2     private int count  = 0;
 3 
 4     @Override
 5     public /*synchronized */ void run() {
 6         System.out.println(Thread.currentThread().getName()
 7                 +" count =" + count++);
 8     }
 9 
10     public static void main(String[] args) {
11         TestSynchronized03 testSynchronized03 = new TestSynchronized03();
12         for (int i = 0; i < 10 ; i++) {
13             new Thread(testSynchronized03, "Thread --- " + i).start();
14         }
15     }
16 }

  我们发下下面的结果少加了一个1,这就是原子性的问题。在synchronized关键字没有使用的时候,对于变量count而言(由多个线程访问),是不能保证原子性(某一段代码从开始运行到结束不能分步执行)的,上面的代码没有使用同步,那么很显然多线程对变量进行加操作就可能会在同一时刻只进行1次加操作

  

  5)关于同步方法和非同步方法:同步方法只影响  锁定同一个锁对象的同步方法,不影响非同步方法被其他线程调用,也不影响其他所资源的同步方法(简单理解就是锁的不是同一个资源,就不会影响);

 1 package cn.test.juc;
 2 
 3 public class TestSynchronized04 {
 4 
 5     private Object o = new Object();
 6 
 7     //同步方法
 8     public synchronized void m1() {
 9         System.out.println("public synchronized void m1() start.");
10 
11         try {
12             Thread.sleep(3000);
13         } catch (InterruptedException e) {
14             e.printStackTrace();
15         }
16 
17         System.out.println("public synchronized void m1() end.");
18     }
19 
20     public void m3() {
21         synchronized (o) {
22             System.out.println("public void m3() start.");
23             try {
24                 Thread.sleep(1500);
25             } catch (InterruptedException e) {
26                 e.printStackTrace();
27             }
28             System.out.println("public void m3() end.");
29         }
30     }
31 
32     //非同步方法
33     public void m2() {
34         System.out.println("public void m2() start.");
35         try {
36             Thread.sleep(1500);
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40         System.out.println("public void m2() end.");
41     }
42 
43     public static class MyThread implements Runnable{
44         int i;
45         TestSynchronized04 testSynchronized04;
46         public MyThread(int i, TestSynchronized04 testSynchronized04) {
47             this.i = i;
48             this.testSynchronized04 = testSynchronized04;
49         }
50 
51         @Override
52         public void run() {
53             if(i == 0) {
54                 testSynchronized04.m1();
55             } else if(i == 1) {
56                 testSynchronized04.m3();
57             } else {
58                 testSynchronized04.m2();
59             }
60         }
61     }
62 
63     public static void main(String[] args) {
64         TestSynchronized04 testSynchronized04 = new TestSynchronized04();
65         new Thread(new TestSynchronized04.MyThread(0, testSynchronized04)).start();
66         new Thread(new TestSynchronized04.MyThread(1, testSynchronized04)).start();
67         new Thread(new TestSynchronized04.MyThread(2, testSynchronized04)).start();
68     }
69 }  

  下面是运行的结果

  

  6)脏读问题

 1 package cn.test.juc;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized05 {
 6     private double d = 0.0;
 7 
 8     //相当与是set方法
 9     public synchronized void m1(double d) {
10         try {
11             TimeUnit.SECONDS.sleep(2);
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15         this.d = d;
16     }
17 
18     //相当于是get方法
19     public double m2() {
20         return this.d;
21     }
22 
23     public static void main(String[] args) {
24         final TestSynchronized05 testSynchronized05 = new TestSynchronized05();
25 
26         new Thread(new Runnable() {
27             @Override
28             public void run() {
29                 testSynchronized05.m1(100);
30             }
31         }).start();
32 
33         System.out.println(testSynchronized05.m2());
34         try {
35             TimeUnit.SECONDS.sleep(3);
36         } catch (InterruptedException e) {
37             e.printStackTrace();
38         }
39         System.out.println(testSynchronized05.m2());
40     }
41 }

  上面代码的输出是0.0  100.00,而不是期望的100.00  100.00,出现这种情况(脏读)的原因是什么的?就是m1方法的这段代码引起的

  

  这段代码表示睡眠2秒之后再进行set操作,使用这一段代码的原因就是模拟实际当中的复杂处理操作,可能会比较耗时,但是这时候还没执行完毕没有将正确的结果写会,别的线程就去访问临界资源的话,就会出现脏读的情况。

  7)锁的可重入问题:同一个线程,多次调用同步代码,锁定同一个对象,可重入

  看看下面的代码实例:main调用m1方法,m1方法中调用m2方法,两个方法锁定的都是this对象,就会是上面说到的这种情况

 1 package cn.test.juc;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized06 {
 6 
 7     synchronized void m1() { //锁this
 8         System.out.println("m1 start()");
 9         try {
10             TimeUnit.SECONDS.sleep(2);
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14         m2();
15         System.out.println("m1 end()");
16     }
17 
18     synchronized void m2() { //锁this
19         System.out.println("m2 start()");
20         try {
21             TimeUnit.SECONDS.sleep(1);
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25         System.out.println("m2 end()");
26     }
27 
28     public static void main(String[] args) {
29         new TestSynchronized06().m1();
30     }
31 }

  8)关于同步的继承问题:同一个线程中,子类同步方法覆盖父类的同步方法,可以指定调用父类的同步方法(相当于锁的重入);

 1 package cn.test.juc;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized07 {
 6     synchronized void m() {
 7         System.out.println("Super Class m start");
 8         try {
 9             TimeUnit.SECONDS.sleep(1);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13         System.out.println("Super Class m end");
14     }
15 
16     public static void main(String[] args) {
17         new ExtendTest07().m();
18     }
19 }
20 
21 class ExtendTest07 extends TestSynchronized07 {
22     synchronized void m() {
23         System.out.println("Sub Class m start");
24         super.m();
25         System.out.println("Sub Class m end");
26     }
27 }

  9)锁与异常:当同步方法出现异常的时候会自动释放锁,不会影响其他线程的执行

 1 package cn.test.juc;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized08 {
 6     int i = 0;
 7     synchronized void m(){
 8         System.out.println(Thread.currentThread().getName() + " - start");
 9         while(true){
10             i++;
11             System.out.println(Thread.currentThread().getName() + " - " + i);
12             try {
13                 TimeUnit.SECONDS.sleep(1);
14             } catch (InterruptedException e) {
15                 // TODO Auto-generated catch block
16                 e.printStackTrace();
17             }
18             if(i == 5){
19                 i = 1/0;
20             }
21         }
22     }
23 
24     public static void main(String[] args) {
25         final TestSynchronized08 t = new TestSynchronized08();
26         new Thread(new Runnable() {
27             @Override
28             public void run() {
29                 t.m();
30             }
31         }, "t1").start();
32 
33         new Thread(new Runnable() {
34             @Override
35             public void run() {
36                 t.m();
37             }
38         }, "t2").start();
39     }
40 }

  下面是输出的结果:

  

  10)synchronized锁的是对象,而不是引用:同步代码一旦加锁之后会有一个临时锁引用执行锁对象,和真实的引用无直接关联,在锁释放之前,修改锁对象引用不会影响同步代码块的执行

 1 package cn.test.syn;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized09 {
 6     Object o = new Object();
 7 
 8     int i = 0;
 9     int a(){
10         try{
11             /*
12              * return i ->
13              * int _returnValue = i; // 0;
14              * return _returnValue;
15              */
16             return i;
17         }finally{
18             i = 10;
19         }
20     }
21 
22     void m(){
23         System.out.println(Thread.currentThread().getName() + " start");
24         synchronized (o) {
25             while(true){
26                 try {
27                     TimeUnit.SECONDS.sleep(1);
28                 } catch (InterruptedException e) {
29                     e.printStackTrace();
30                 }
31                 System.out.println(Thread.currentThread().getName() + " - " + o);
32             }
33         }
34     }
35 
36     public static void main(String[] args) {
37         final TestSynchronized09 t = new TestSynchronized09();
38         new Thread(new Runnable() {
39             @Override
40             public void run() {
41                 t.m();
42             }
43         }, "thread1").start();
44         try {
45             TimeUnit.SECONDS.sleep(3);
46         } catch (InterruptedException e) {
47             e.printStackTrace();
48         }
49         Thread thread2 = new Thread(new Runnable() {
50             @Override
51             public void run() {
52                 t.m();
53             }
54         }, "thread2");
55         t.o = new Object();
56         thread2.start();
57 
58         System.out.println(t.i);
59         System.out.println(t.a());
60         System.out.println(t.i);
61     }
62 }

  11)synchronized中的常量问题:在定义同步代码块的时候,不要使用常量对象作为锁对象

 1 package cn.test.syn;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestSynchronized09 {
 6     Object o = new Object();
 7 
 8     int i = 0;
 9     int a(){
10         try{
11             /*
12              * return i ->
13              * int _returnValue = i; // 0;
14              * return _returnValue;
15              */
16             return i;
17         }finally{
18             i = 10;
19         }
20     }
21 
22     void m(){
23         System.out.println(Thread.currentThread().getName() + " start");
24         synchronized (o) {
25             while(true){
26                 try {
27                     TimeUnit.SECONDS.sleep(1);
28                 } catch (InterruptedException e) {
29                     e.printStackTrace();
30                 }
31                 System.out.println(Thread.currentThread().getName() + " - " + o);
32             }
33         }
34     }
35 
36     public static void main(String[] args) {
37         final TestSynchronized09 t = new TestSynchronized09();
38         new Thread(new Runnable() {
39             @Override
40             public void run() {
41                 t.m();
42             }
43         }, "thread1").start();
44         try {
45             TimeUnit.SECONDS.sleep(3);
46         } catch (InterruptedException e) {
47             e.printStackTrace();
48         }
49         Thread thread2 = new Thread(new Runnable() {
50             @Override
51             public void run() {
52                 t.m();
53             }
54         }, "thread2");
55         t.o = new Object();
56         thread2.start();
57 
58         System.out.println(t.i);
59         System.out.println(t.a());
60         System.out.println(t.i);
61     }
62 }

二、Volatile关键字

   1、下面的代码在没有使用volatile之前,是不会从循环中跳出的(main线程和新创建的线程互相之间是不可见的,所以新创建的线程在使用m方法的时候并不知道main线程已经改变了b的值,所以不会跳出循环),那么使用volatile会怎样呢(简单说是可见性)但是啥是可见性:当某个线程正在使用对象状态,而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

 1 package cn.test.Volatile;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class TestVolatile01 {
 6     /*volatile*/ boolean b = true;
 7 
 8     void m(){
 9         System.out.println("start");
10         while(b){}
11         System.out.println("end");
12     }
13 
14     public static void main(String[] args) {
15         final TestVolatile01 t = new TestVolatile01();
16         new Thread(new Runnable() {
17             @Override
18             public void run() {
19                 t.m();
20             }
21         }).start();
22 
23         try {
24             TimeUnit.SECONDS.sleep(1);
25         } catch (InterruptedException e) {
26             e.printStackTrace();
27         }
28         t.b = false;
29     }
30 }

  2、volatile只能保证可见性,不能保证原子性,volatile不是加锁问题,只是保证内存数据可见;参照下面的例子,运行的结果不是期望的100000,而是

当然,也不一定每次都是这个值。

 1 package cn.test.Volatile;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class TestVolatile02 {
 7     volatile int count = 0;
 8     /*synchronized*/ void m(){
 9         for(int i = 0; i < 10000; i++){
10             count++;
11         }
12     }
13 
14     public static void main(String[] args) {
15         final TestVolatile02 t = new TestVolatile02();
16         List<Thread> threads = new ArrayList<>();
17         for(int i = 0; i < 10; i++){
18             threads.add(new Thread(new Runnable() {
19                 @Override
20                 public void run() {
21                     t.m();
22                 }
23             }));
24         }
25         for(Thread thread : threads){
26             thread.start();
27         }
28         for(Thread thread : threads){
29             try {
30                 thread.join();
31             } catch (InterruptedException e) {
32                 // TODO Auto-generated catch block
33                 e.printStackTrace();
34             }
35         }
36         System.out.println(t.count);
37     }
38 }

 三、AtomicXXX

  Atomic主要做的就是原子操作,其中的每个方法都是原子操作,可以保证线程安全。参照下面的例子:创建十个线程,每个线程累加100次,得到的结果就是1000

 1 package cn.test.atomic;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.atomic.AtomicInteger;
 6 
 7 public class TestAtomic01 {
 8     AtomicInteger count = new AtomicInteger(0);
 9     void m1(){
10         for(int i = 0; i < 100; i++){
11             /*if(count.get() < 1000)*/
12             count.incrementAndGet();
13         }
14     }
15 
16     public static void main(String[] args) {
17         final TestAtomic01 t = new TestAtomic01();
18         List<Thread> threads = new ArrayList<>();
19         for(int i = 0; i < 10; i++){
20             threads.add(new Thread(new Runnable() {
21                 @Override
22                 public void run() {
23                     t.m1();
24                 }
25             }));
26         }
27         for(Thread thread : threads){
28             thread.start();
29         }
30         for(Thread thread : threads){
31             try {
32                 thread.join();
33             } catch (InterruptedException e) {
34                 // TODO Auto-generated catch block
35                 e.printStackTrace();
36             }
37         }
38         System.out.println(t.count.intValue());
39     }
40 }

 四、CountDownLatch

 1 package cn.test.syn;
 2 /**
 3  * 门闩 - CountDownLatch
 4  * 可以和锁混合使用,或替代锁的功能。
 5  * 在门闩未完全开放之前等待。当门闩完全开放后执行。
 6  * 避免锁的效率低下问题。
 7  */
 8 import java.util.concurrent.CountDownLatch;
 9 import java.util.concurrent.TimeUnit;
10 
11 public class Test {
12     CountDownLatch latch = new CountDownLatch(5);
13 
14     void m1(){
15         try {
16             latch.await();// 等待门闩开放。
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20         System.out.println("m1() method");
21     }
22 
23     void m2(){
24         for(int i = 0; i < 10; i++){
25             if(latch.getCount() != 0){
26                 System.out.println("latch count : " + latch.getCount());
27                 latch.countDown(); // 减门闩上的锁。
28             }
29             try {
30                 TimeUnit.MILLISECONDS.sleep(500);
31             } catch (InterruptedException e) {
32                 // TODO Auto-generated catch block
33                 e.printStackTrace();
34             }
35             System.out.println("m2() method : " + i);
36         }
37     }
38 
39     public static void main(String[] args) {
40         final Test t = new Test();
41         new Thread(new Runnable() {
42             @Override
43             public void run() {
44                 t.m1();
45             }
46         }).start();
47 
48         new Thread(new Runnable() {
49             @Override
50             public void run() {
51                 t.m2();
52             }
53         }).start();
54     }
55 }

 

posted @ 2019-02-28 01:58  夏末秋涼  阅读(680)  评论(0编辑  收藏  举报