1.为什么要使用synchronized?
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)
2.synchronized锁什么,加锁的目的是什么?
synchronized锁的是对象,锁的的对象可能是this、临界资源对象、class类对象
加锁的目的是保证操作的原子性
3.代码示例
3.1锁this和临界资源对象
本例中:
- testSync1是synchronized代码块,锁的是object,临界资源对象
- testSync2和testSync3都是锁的是当前对象this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
package com.bernardlowe.concurrent.t01;
import java.util.concurrent.TimeUnit;
public class Test_01 { private int count = 0; private Object object = new Object();
public void testSync1(){ synchronized(object){ System.out.println(Thread.currentThread().getName() + " count = " + count++); } }
public void testSync2(){ synchronized(this){ System.out.println(Thread.currentThread().getName() + " count = " + count++); } }
public synchronized void testSync3(){ System.out.println(Thread.currentThread().getName() + " count = " + count++); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final Test_01 t = new Test_01(); new Thread(new Runnable() { @Override public void run() { t.testSync3(); } }).start(); new Thread(new Runnable() { @Override public void run() { t.testSync3(); } }).start(); } }
|
3.2锁class类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
package com.bernardlowe.concurrent.t01;
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) { e.printStackTrace(); } } public static void testSync5(){ synchronized(Test_02.class){ System.out.println(Thread.currentThread().getName() + " staticCount = " + staticCount++); } } }
|
3.3 什么时候锁临界资源,什么时候锁当前对象?
如果在加锁的时候需要对当前对象的访问限定,建议锁临界资源(即锁一个临界资源),如果对当前锁级别比较高的话,就锁当前对象。建议都以同步代码块的方式进行开发,这样可以避免锁的范围太高
同步方法和非同步方法
提问:同步方法是否影响其他线程调用非同步方法,或调用其他锁资源的同步方法?
代码示例
m1是非同步方法,m2是同步方法,m3同步代码块,锁的临界资源,这段代码的目的是为了证明在调用同步方法m1时,m2,m3是否能够执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
package com.bernardlowe.concurrent.t01;
public class Test_04 { Object o = new Object(); 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{ public MyThread01(int i, Test_04 t){ this.i = i; this.t = t; } int i ; Test_04 t; public void run(){ if(i == 0){ t.m1(); }else if (i > 0){ t.m2(); }else { t.m3(); } } } public static void main(String[] args) { Test_04 t = new Test_04(); new Thread(new MyThread01(0, t)).start(); new Thread(new MyThread01(1, t)).start(); new Thread(new MyThread01(-1, t)).start(); } }
|
执行结果
可以看出,在执行同步方法m1时,m2,m3不受影响,依然可以执行
结论:同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
但有一点需要注意,尽量在商业开发中避免同步方法。使用同步代码块。 细粒度解决同步问题。
3.3 重入锁
这里重入锁分为两类:
- 1.在同步方法里面调用其他同步方法
- 2.子类同步方法覆盖父类同步方法
下面来看第一种:在同步方法里面调用其他同步方法
思考:调用m1()方法,m2()方法是否会执行?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
package com.bernardlowe.concurrent.t01;
import java.util.concurrent.TimeUnit;
public class Test_06 { synchronized void m1(){ System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } m2(); System.out.println("m1 end"); } synchronized void m2(){ System.out.println("m2 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2 end"); } public static void main(String[] args) { new Test_06().m1(); } }
|
结果:在m1()方法调用时,m2()方法依然可执行。多次调用同步代码,锁定同一个锁对象,是可重入的
第二种情况:子类同步方法覆盖父类同步方法
思考:子类同步方法m()中,调用父类同步方法m(),是否可重入?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
package com.bernardlowe.concurrent.t01;
import java.util.concurrent.TimeUnit;
public class Test_07 { 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"); } public static void main(String[] args) { new Sub_Test_07().m(); } }
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"); } }
|
结果:子类同步方法m()中,调用了父类同步方法m(),可以重入
3.4 锁与异常
思考:当同步方法或同步代码块中发生异常,是否会影响其他线程的执行?
下面来看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
package com.bernardlowe.concurrent.t01;
import java.util.concurrent.TimeUnit;
public class Test_08 { int i = 0; 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) { e.printStackTrace(); } if(i == 5){ i = 1/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(); } }
|
这段代码中先运行了两个线程t1、t2,当其中一个线程发生异常时,另外一个线程是否能继续执行
结果:当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。
思考: 同步业务逻辑中,如果发生异常如何处理?
比如上面会发生异常的代码中,可以这样
1 2 3 4 5 6 7
| if(i == 5){ try { i = 1/0; } catch (Exception e) { i = 0; } }
|
3.5 锁对象变更问题
代码示例:8
思考:当一个线程执行同步方法时,另一个线程修改了锁对象,是否还能执行同步代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
package com.bernardlowe.concurrent.t01;
import java.util.concurrent.TimeUnit;
public class Test_13 { Object o = new Object(); 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); } } } 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(); } }
|
结论:可以看出,其他线程依然可以执行同步方法
因为同步代码一旦加锁后,那么会有一个临时的锁引用执行锁对象,和真实的引用无直接关联。在锁未释放之前,修改锁对象引用,不会影响同步代码的执行。
注意:不要使用静态常量作为锁对象
如下代码,因为String常量池的问题,s1,s1是同一个对象,所以m1,m2方法锁的是也同一个对象,m1同步方法被执行后,m2方法不会被执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
package com.bernardlowe.concurrent.t01;
public class Test_14 { String s1 = "hello"; String s2 = "hello"; void m1(){ synchronized (s1) { System.out.println("m1()"); while(true){ } } } void m2(){ synchronized (s2) { System.out.println("m2()"); while(true){ } } } 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(); } }
|
更多精彩内容:mrxccc