synchronized关键字
锁对象。synchronized(this)和synchronized方法都是锁当前对象。
import java.util.concurrent.TimeUnit; public class Test_01 { private int count = 0; private Object o = new Object(); public static void main(String[] args) { final Test_01 t = new Test_01(); new Thread(new Runnable() { @Override public void run() { t.testSync2(); } }, "testSync2").start(); new Thread(new Runnable() { @Override public void run() { t.testSync1(); } }, "testSync1").start(); new Thread(new Runnable() { @Override public void run() { t.testSync3(); } }, "testSync3").start(); } public void testSync1() { synchronized (o) { System.out.println(Thread.currentThread().getName() + " count = " + count++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } public void testSync2() { synchronized (this) { System.out.println(Thread.currentThread().getName() + " count = " + count++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void testSync3() { System.out.println(Thread.currentThread().getName() + " count = " + count++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } }
同步方法 - static:静态同步方法,锁的是当前类型的类对象。在代码中就是类名.class
import java.util.concurrent.TimeUnit; public class Test_02 { private static int staticCount = 0; public static synchronized void testSync4() { System.out.println(Thread.currentThread().getName() + " staticCount = " + staticCount++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void testSync5() { synchronized (Test_02.class) { System.out.println(Thread.currentThread().getName() + " staticCount = " + staticCount++); } } }
同步方法 - 原子性
加锁的目的: 就是为了保证操作的原子性。
public class Test_03 implements Runnable { private int count = 0; public static void main(String[] args) { Test_03 t = new Test_03(); for (int i = 0; i < 5; i++) { new Thread(t, "Thread - " + i).start(); } } @Override public /*synchronized*/ void run() { System.out.println(Thread.currentThread().getName() + " count = " + count++); } }
同步方法 - 同步方法和非同步方法的调用
同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
public class Test_04 { Object o = new Object(); public static void main(String[] args) { Test_04 t = new Test_04(); new Thread(new Test_04.MyThread01(0, t)).start(); new Thread(new Test_04.MyThread01(1, t)).start(); new Thread(new Test_04.MyThread01(-1, t)).start(); } public synchronized void m1() { // 重量级的访问操作。 System.out.println("public synchronized void m1() start"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public synchronized void m1() end"); } public void m3() { synchronized (o) { System.out.println("public void m3() start"); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public void m3() end"); } } public void m2() { System.out.println("public void m2() start"); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("public void m2() end"); } public static class MyThread01 implements Runnable { int i; Test_04 t; public MyThread01(int i, Test_04 t) { this.i = i; this.t = t; } public void run() { if (i == 0) { t.m1(); } else if (i > 0) { t.m2(); } else { t.m3(); } } } }
结果:
public synchronized void m1() start public void m2() start public void m3() start public void m2() end public void m3() end public synchronized void m1() end
同步方法 - 多方法调用原子性问题(业务)
同步方法只能保证当前方法的原子性,不能保证多个业务方法之间的互相访问的原子性。
注意:在商业开发中,多方法要求结果访问原子操作,需要多个方法都加锁,且锁定统一个资源。一般来说,商业项目中,不考虑业务逻辑上的脏读问题。
import java.util.concurrent.TimeUnit; public class Test_05 { private double d = 0.0; public static void main(String[] args) { final Test_05 t = new Test_05(); new Thread(new Runnable() { @Override public void run() { t.m1(100); } }).start(); System.out.println(t.m2()); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.m2()); } public synchronized void m1(double d) { try { // 相当于复杂的业务逻辑代码。 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } this.d = d; } public double m2() { return this.d; } }
锁可重入: 同一个线程,多次调用同步代码,锁定同一个锁对象,可重入。
import java.util.concurrent.TimeUnit; public class Test_06 { public static void main(String[] args) { new Test_06().m1(); } synchronized void m1() { // 锁this System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } m2(); System.out.println("m1 end"); } synchronized void m2() { // 锁this System.out.println("m2 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2 end"); } }
同步方法 - 继承::类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。相当于锁的重入。
import java.util.concurrent.TimeUnit; public class Test_07 { public static void main(String[] args) { new Sub_Test_07().m(); } synchronized void m() { System.out.println("Super Class m start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Super Class m end"); } } class Sub_Test_07 extends Test_07 { synchronized void m() { System.out.println("Sub Class m start"); super.m(); System.out.println("Sub Class m end"); } }
同步方法 - 锁与异常:当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。注意同步业务逻辑中,如果发生异常如何处理。
import java.util.concurrent.TimeUnit; public class Test_08 { int i = 0; public static void main(String[] args) { final Test_08 t = new Test_08(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "t1").start(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "t2").start(); } synchronized void m() { System.out.println(Thread.currentThread().getName() + " - start"); while (true) { i++; System.out.println(Thread.currentThread().getName() + " - " + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (i == 5) { i = 1 / 0; } } } }
结果:
t1 - start t1 - 1 t1 - 2 t1 - 3 t1 - 4 t1 - 5 Exception in thread "t1" java.lang.ArithmeticException: / by zero t2 - start at concurrent.t01.Test_08.m(Test_08.java:43) t2 - 6 at concurrent.t01.Test_08$1.run(Test_08.java:19) at java.base/java.lang.Thread.run(Thread.java:844) t2 - 7 t2 - 8 t2 - 9 t2 - 10
volatile关键字
volatile的可见性:通知OS操作系统底层,在CPU计算过程中,都要检查内存中数据的有效性。保证最新的内存数据被使用。
import java.util.concurrent.TimeUnit; public class Test_09 { volatile boolean b = true; public static void main(String[] args) { final Test_09 t = new Test_09(); new Thread(new Runnable() { @Override public void run() { t.m(); } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } t.b = false; } void m() { System.out.println("start"); while (b) { } System.out.println("end"); } }
volatile的非原子性问题:只能保证可见性,不能保证原子性。不是枷锁问题,只是内存数据可见。
public class Test_10 { volatile int count = 0; public static void main(String[] args) { final Test_10 t = new Test_10(); List<Thread> threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(new Runnable() { @Override public void run() { t.m(); } })); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(t.count); } /*synchronized*/ void m() { for (int i = 0; i < 10000; i++) { count++; } } }
AtomicXxx
同步类型:原子操作类型。 其中的每个方法都是原子操作。可以保证线程安全。
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Test_11 { AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { final Test_11 t = new Test_11(); List<Thread> threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(new Runnable() { @Override public void run() { t.m(); } })); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(t.count.intValue()); } void m() { for (int i = 0; i < 10000; i++) { /*if(count.get() < 1000)*/ count.incrementAndGet(); } } }
同步粒度问题:尽量在商业开发中避免同步方法。使用同步代码块。 细粒度解决同步问题。可以提高效率。
public class Test_12 { synchronized void m1() { // 前置逻辑 System.out.println("同步逻辑"); // 后置逻辑 } void m2() { // 前置逻辑 synchronized (this) { System.out.println("同步逻辑"); } // 后置逻辑 } }
对象变更问题:同步代码一旦加锁后,那么会有一个临时的锁引用执行锁对象,和真实的引用无直接关联。在锁未释放之前,修改锁对象引用,不会影响同步代码的执行。
public class Test_13 { Object o = new Object(); int i = 0; public static void main(String[] args) { final Test_13 t = new Test_13(); new Thread(new Runnable() { @Override public void run() { t.m(); } }, "thread1").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } Thread thread2 = new Thread(new Runnable() { @Override public void run() { t.m(); } }, "thread2"); t.o = new Object(); thread2.start(); System.out.println(t.i); System.out.println(t.a()); System.out.println(t.i); } int a() { try { /* * return i -> * int _returnValue = i; // 0; * return _returnValue; */ return i; } finally { i = 10; } } void m() { System.out.println(Thread.currentThread().getName() + " start"); synchronized (o) { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " - " + o); } } } }
结果:
thread1 start thread1 - java.lang.Object@6018fac7 thread1 - java.lang.Object@6018fac7 thread1 - java.lang.Object@6018fac7 0 0 10 thread2 start thread1 - java.lang.Object@6edf18b4 thread2 - java.lang.Object@6edf18b4 thread1 - java.lang.Object@6edf18b4 thread2 - java.lang.Object@6edf18b4 thread1 - java.lang.Object@6edf18b4
常量问题:在定义同步代码块时,不要使用常量对象作为锁对象。
i1、i2会实现m1、m2方法的同步;s1、s2是不同的对象,不能实现m1、m2方法的同步。
public class Test_14 { String s1 = "hello"; String s2 = new String("hello"); // new关键字,一定是在堆中创建一个新的对象。 Integer i1 = 1; Integer i2 = 1; public static void main(String[] args) { final Test_14 t = new Test_14(); new Thread(new Runnable() { @Override public void run() { t.m1(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.m2(); } }).start(); } void m1() { synchronized (i1) { System.out.println("m1()"); while (true) { } } } void m2() { synchronized (i2) { System.out.println("m2()"); while (true) { } } } }
门闩 - CountDownLatch
可以和锁混合使用,或替代锁的功能。在门闩未完全开放之前等待。当门闩完全开放后执行。避免锁的效率低下问题。
import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class Test_15 { CountDownLatch latch = new CountDownLatch(5); public static void main(String[] args) { final Test_15 t = new Test_15(); new Thread(new Runnable() { @Override public void run() { t.m1(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.m2(); } }).start(); } void m1() { try { latch.await();// 等待门闩开放。 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m1() method"); } void m2() { for (int i = 0; i < 10; i++) { if (latch.getCount() != 0) { System.out.println("latch count : " + latch.getCount()); latch.countDown(); // 减门闩上的锁。 } try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("m2() method : " + i); } } }
结果:
latch count : 5 m2() method : 0 latch count : 4 m2() method : 1 latch count : 3 m2() method : 2 latch count : 2 m2() method : 3 latch count : 1 m1() method m2() method : 4 m2() method : 5 m2() method : 6
...
练习题
自定义容器,提供新增元素(add)和获取元素数量(size)方法。
启动两个线程。线程1向容器中新增10个数据。线程2监听容器元素数量,当容器元素数量为5时,线程2输出信息并终止。
方法一(volatile的可见性):
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class Test_01 { public static void main(String[] args) { final Test_01_Container t = new Test_01_Container(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("add Object to Container " + i); t.add(new Object()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { if (t.size() == 5) { System.out.println("size = 5"); break; } } } }).start(); } } class Test_01_Container { volatile List<Object> container = new ArrayList<>(); public void add(Object o) { this.container.add(o); } public int size() { return this.container.size(); } }
方法二(synchornized):
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class Test_02 { public static void main(String[] args) { final Test_02_Container t = new Test_02_Container(); final Object lock = new Object(); new Thread(new Runnable() { @Override public void run() { synchronized (lock) { if (t.size() != 5) { try { lock.wait(); // 线程进入等待队列。 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("size = 5"); lock.notifyAll(); // 唤醒其他等待线程 } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (lock) { for (int i = 0; i < 10; i++) { System.out.println("add Object to Container " + i); t.add(new Object()); if (t.size() == 5) { lock.notifyAll(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } }).start(); } } class Test_02_Container { List<Object> container = new ArrayList<>(); public void add(Object o) { this.container.add(o); } public int size() { return this.container.size(); } }
方法三(门闩):
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class Test_03 { public static void main(String[] args) { final Test_03_Container t = new Test_03_Container(); final CountDownLatch latch = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { if (t.size() != 5) { try { latch.await(); // 等待门闩的开放。 不是进入等待队列 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("size = 5"); } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("add Object to Container " + i); t.add(new Object()); if (t.size() == 5) { latch.countDown(); // 门闩-1 } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } class Test_03_Container { List<Object> container = new ArrayList<>(); public void add(Object o) { this.container.add(o); } public int size() { return this.container.size(); } }