java 并发 同步_Java并发编程基础——同步

一、synchronized 关键字

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

1 packagecn.test.juc;2

3 public classTestSynchronized {4

5 private int count = 0;6 private Object object = newObject();7

8

9 public voidtestSyn1() {10 //锁对象(这里面是锁临界资源)

11 synchronized(object) {12 System.out.println(Thread.currentThread().getName()13 +" count =" + count++);14 }15 }16

17 public voidtestSyn2() {18 //锁当前对象

19 synchronized (this) {20 System.out.println(Thread.currentThread().getName()21 +" count =" + count++);22 }23 }24

25 //锁当前对象

26 public synchronized voidtestSyn3() {27 System.out.println(Thread.currentThread().getName()28 +" count =" + count++);29 }30 }

2)如果在加锁的时候对当前对象的访问限定要求比较低的时候,建议锁某一段代码或者某一个对象;如果访问限定要求比较高的话,建议锁当前对象。简单而言就可以说是减小锁的范围。对于锁当前对象

11540e8c9a3a37cb5beb7a61d4aa58b0.png或者

fb1f91ca21fe795016cb81f6cd7ad891.png都是重量级锁,什么意思呢,“就是任意多个线程,多个资源不会被多个线程访问所影响的”

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

1 public classTestSynchronized02 {2 private static int staticCount = 0;3

4 //静态同步方法,锁的是当前类型的类对象(即TestSynchronized02.class)

5 public static synchronized voidtestSyn1() {6 System.out.println(Thread.currentThread().getName()7 +" staticCount =" + staticCount++);8 }9

10 //下面的这种方式也是锁当前类型的类对象

11 public static voidtestSyn2() {12 synchronized (TestSynchronized02.class) {13 System.out.println(Thread.currentThread().getName()14 +" staticCount =" + staticCount++);15 }16 }17 }

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

1 public class TestSynchronized03 implementsRunnable{2 private int count = 0;3

4 @Override5 public /*synchronized*/ voidrun() {6 System.out.println(Thread.currentThread().getName()7 +" count =" + count++);8 }9

10 public static voidmain(String[] args) {11 TestSynchronized03 testSynchronized03 = newTestSynchronized03();12 for (int i = 0; i < 10 ; i++) {13 new Thread(testSynchronized03, "Thread --- " +i).start();14 }15 }16 }

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

d6ded81b21d1a11d84036528e38d2150.png

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

1 packagecn.test.juc;2

3 public classTestSynchronized04 {4

5 private Object o = newObject();6

7 //同步方法

8 public synchronized voidm1() {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 voidm3() {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 voidm2() {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 implementsRunnable{44 inti;45 TestSynchronized04 testSynchronized04;46 public MyThread(inti, TestSynchronized04 testSynchronized04) {47 this.i =i;48 this.testSynchronized04 =testSynchronized04;49 }50

51 @Override52 public voidrun() {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 voidmain(String[] args) {64 TestSynchronized04 testSynchronized04 = newTestSynchronized04();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 }

下面是运行的结果

5237c2c3f68958d1aec787e73e6f737f.png

6)脏读问题

1 packagecn.test.juc;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized05 {6 private double d = 0.0;7

8 //相当与是set方法

9 public synchronized void m1(doubled) {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 doublem2() {20 return this.d;21 }22

23 public static voidmain(String[] args) {24 final TestSynchronized05 testSynchronized05 = newTestSynchronized05();25

26 new Thread(newRunnable() {27 @Override28 public voidrun() {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方法的这段代码引起的

7a105230fb1440eb119ae9153bb05be7.png

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

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

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

1 packagecn.test.juc;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized06 {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 voidmain(String[] args) {29 newTestSynchronized06().m1();30 }31 }

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

1 packagecn.test.juc;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized07 {6 synchronized voidm() {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 voidmain(String[] args) {17 newExtendTest07().m();18 }19 }20

21 class ExtendTest07 extendsTestSynchronized07 {22 synchronized voidm() {23 System.out.println("Sub Class m start");24 super.m();25 System.out.println("Sub Class m end");26 }27 }

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

1 packagecn.test.juc;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized08 {6 int i = 0;7 synchronized voidm(){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 voidmain(String[] args) {25 final TestSynchronized08 t = newTestSynchronized08();26 new Thread(newRunnable() {27 @Override28 public voidrun() {29 t.m();30 }31 }, "t1").start();32

33 new Thread(newRunnable() {34 @Override35 public voidrun() {36 t.m();37 }38 }, "t2").start();39 }40 }

下面是输出的结果:

5f2f93e723db37ff8f06a35fac4587c4.png

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

4906bf4d11430230ae11b0d192976ed0.png

1 packagecn.test.syn;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized09 {6 Object o = newObject();7

8 int i = 0;9 inta(){10 try{11 /*

12 * return i ->13 * int _returnValue = i; // 0;14 * return _returnValue;15 */

16 returni;17 }finally{18 i = 10;19 }20 }21

22 voidm(){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 voidmain(String[] args) {37 final TestSynchronized09 t = newTestSynchronized09();38 new Thread(newRunnable() {39 @Override40 public voidrun() {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(newRunnable() {50 @Override51 public voidrun() {52 t.m();53 }54 }, "thread2");55 t.o = newObject();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 packagecn.test.syn;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestSynchronized09 {6 Object o = newObject();7

8 int i = 0;9 inta(){10 try{11 /*

12 * return i ->13 * int _returnValue = i; // 0;14 * return _returnValue;15 */

16 returni;17 }finally{18 i = 10;19 }20 }21

22 voidm(){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 voidmain(String[] args) {37 final TestSynchronized09 t = newTestSynchronized09();38 new Thread(newRunnable() {39 @Override40 public voidrun() {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(newRunnable() {50 @Override51 public voidrun() {52 t.m();53 }54 }, "thread2");55 t.o = newObject();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会怎样呢(简单说是可见性)但是啥是可见性:当某个线程正在使用对象状态,而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

433f7f972f73b7d787a99672882cda79.png

1 packagecn.test.Volatile;2

3 importjava.util.concurrent.TimeUnit;4

5 public classTestVolatile01 {6 /*volatile*/ boolean b = true;7

8 voidm(){9 System.out.println("start");10 while(b){}11 System.out.println("end");12 }13

14 public static voidmain(String[] args) {15 final TestVolatile01 t = newTestVolatile01();16 new Thread(newRunnable() {17 @Override18 public voidrun() {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,而是

f4b307cea6aaf73bab138ccf972c76f4.png

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

1 packagecn.test.Volatile;2

3 importjava.util.ArrayList;4 importjava.util.List;5

6 public classTestVolatile02 {7 volatile int count = 0;8 /*synchronized*/ voidm(){9 for(int i = 0; i < 10000; i++){10 count++;11 }12 }13

14 public static voidmain(String[] args) {15 final TestVolatile02 t = newTestVolatile02();16 List threads = new ArrayList<>();17 for(int i = 0; i < 10; i++){18 threads.add(new Thread(newRunnable() {19 @Override20 public voidrun() {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 packagecn.test.atomic;2

3 importjava.util.ArrayList;4 importjava.util.List;5 importjava.util.concurrent.atomic.AtomicInteger;6

7 public classTestAtomic01 {8 AtomicInteger count = new AtomicInteger(0);9 voidm1(){10 for(int i = 0; i < 100; i++){11 /*if(count.get() < 1000)*/

12 count.incrementAndGet();13 }14 }15

16 public static voidmain(String[] args) {17 final TestAtomic01 t = newTestAtomic01();18 List threads = new ArrayList<>();19 for(int i = 0; i < 10; i++){20 threads.add(new Thread(newRunnable() {21 @Override22 public voidrun() {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 packagecn.test.syn;2 /**

3 * 门闩 - CountDownLatch4 * 可以和锁混合使用,或替代锁的功能。5 * 在门闩未完全开放之前等待。当门闩完全开放后执行。6 * 避免锁的效率低下问题。7 */

8 importjava.util.concurrent.CountDownLatch;9 importjava.util.concurrent.TimeUnit;10

11 public classTest {12 CountDownLatch latch = new CountDownLatch(5);13

14 voidm1(){15 try{16 latch.await();//等待门闩开放。

17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20 System.out.println("m1() method");21 }22

23 voidm2(){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 voidmain(String[] args) {40 final Test t = newTest();41 new Thread(newRunnable() {42 @Override43 public voidrun() {44 t.m1();45 }46 }).start();47

48 new Thread(newRunnable() {49 @Override50 public voidrun() {51 t.m2();52 }53 }).start();54 }55 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值