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 }