架构师养成记--3.synchronized细节问题
一、synchronized有锁重入的特点,某个线程得到对象的锁后,再次请求此对象可以再次得到改对象的锁。如下示例,在method1中调用method2,在method2中调用method3,而method1、method2和method3都是加了synchronized关键字的。
1 /** 2 * synchronized的重入 3 * @author alienware 4 * 5 */ 6 public class SyncDubbo1 { 7 8 public synchronized void method1(){ 9 System.out.println("method1.."); 10 method2(); 11 } 12 public synchronized void method2(){ 13 System.out.println("method2.."); 14 method3(); 15 } 16 public synchronized void method3(){ 17 System.out.println("method3.."); 18 } 19 20 public static void main(String[] args) { 21 final SyncDubbo1 sd = new SyncDubbo1(); 22 Thread t1 = new Thread(new Runnable() { 23 @Override 24 public void run() { 25 sd.method1(); 26 } 27 }); 28 t1.start(); 29 } 30 }
二、父类和子类的方法都是synchronized的,在子类的方法中调用父类的方法,也是线程安全的。
1 /** 2 * synchronized的重入 3 * @author alienware 4 * 5 */ 6 public class SyncDubbo2 { 7 8 static class Main { 9 public int i = 10; 10 public synchronized void operationSup(){ 11 try { 12 i--; 13 System.out.println("Main print i = " + i); 14 Thread.sleep(100); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } 20 21 static class Sub extends Main { 22 public synchronized void operationSub(){ 23 try { 24 while(i > 0) { 25 i--; 26 System.out.println("Sub print i = " + i); 27 Thread.sleep(100); 28 this.operationSup(); 29 } 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 } 34 } 35 36 public static void main(String[] args) { 37 38 Thread t1 = new Thread(new Runnable() { 39 @Override 40 public void run() { 41 Sub sub = new Sub(); 42 sub.operationSub(); 43 } 44 }); 45 46 t1.start(); 47 } 48 49 50 }
执行结果:
三、synchronized方法内抛异常怎么处理
throw RuntimeException打断此线程或者记录日志然后continue,选择哪种方案取决于具体业务要求。
1 /** 2 * synchronized异常 3 * @author alienware 4 * 5 */ 6 public class SyncException { 7 8 private int i = 0; 9 public synchronized void operation(){ 10 while(true){ 11 try { 12 i++; 13 Thread.sleep(100); 14 System.out.println(Thread.currentThread().getName() + " , i = " + i); 15 if(i == 20){ 16 //Integer.parseInt("a"); 17 throw new RuntimeException(); 18 } 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 } 24 25 public static void main(String[] args) { 26 27 final SyncException se = new SyncException(); 28 Thread t1 = new Thread(new Runnable() { 29 @Override 30 public void run() { 31 se.operation(); 32 } 33 },"t1"); 34 t1.start(); 35 } 36 37 38 }
四、synchronized代码块锁,实用起来也会比较灵活
this、class、Object都可以用来作为代码块锁
1 /** 2 * 使用synchronized代码块加锁,比较灵活 3 * @author alienware 4 * 5 */ 6 public class ObjectLock { 7 8 public void method1(){ 9 synchronized (this) { //对象锁 10 try { 11 System.out.println("do method1.."); 12 Thread.sleep(2000); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 } 17 } 18 19 public void method2(){ //类锁 20 synchronized (ObjectLock.class) { 21 try { 22 System.out.println("do method2.."); 23 Thread.sleep(2000); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 } 29 30 private Object lock = new Object(); 31 public void method3(){ //任何对象锁 32 synchronized (lock) { 33 try { 34 System.out.println("do method3.."); 35 Thread.sleep(2000); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 } 40 } 41 42 43 public static void main(String[] args) { 44 45 final ObjectLock objLock = new ObjectLock(); 46 Thread t1 = new Thread(new Runnable() { 47 @Override 48 public void run() { 49 objLock.method1(); 50 } 51 }); 52 Thread t2 = new Thread(new Runnable() { 53 @Override 54 public void run() { 55 objLock.method2(); 56 } 57 }); 58 Thread t3 = new Thread(new Runnable() { 59 @Override 60 public void run() { 61 objLock.method3(); 62 } 63 }); 64 65 t1.start(); 66 t2.start(); 67 t3.start(); 68 69 70 } 71 72 }
五、尽量不要用String常量作为锁
如下代码只会有t1线程运行,但可以new一个String对象。
1 /** 2 * synchronized代码块对字符串的锁,注意String常量池的缓存功能 3 * @author alienware 4 * 5 */ 6 public class StringLock { 7 8 public void method() { 9 //new String("字符串常量") 10 synchronized ("字符串常量") { 11 try { 12 while(true){ 13 System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始"); 14 Thread.sleep(1000); 15 System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束"); 16 } 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 } 22 23 public static void main(String[] args) { 24 final StringLock stringLock = new StringLock(); 25 Thread t1 = new Thread(new Runnable() { 26 @Override 27 public void run() { 28 stringLock.method(); 29 } 30 },"t1"); 31 Thread t2 = new Thread(new Runnable() { 32 @Override 33 public void run() { 34 stringLock.method(); 35 } 36 },"t2"); 37 38 t1.start(); 39 t2.start(); 40 } 41 }
六、一个对象被用作锁时,这个对象内的属性发生变化不会影响锁的使用。
1 /** 2 * 同一对象属性的修改不会影响锁的情况 3 * @author alienware 4 * 5 */ 6 public class ModifyLock { 7 8 private String name ; 9 private int age ; 10 11 public String getName() { 12 return name; 13 } 14 public void setName(String name) { 15 this.name = name; 16 } 17 public int getAge() { 18 return age; 19 } 20 public void setAge(int age) { 21 this.age = age; 22 } 23 24 public synchronized void changeAttributte(String name, int age) { 25 try { 26 System.out.println("当前线程 : " + Thread.currentThread().getName() + " 开始"); 27 this.setName(name); 28 this.setAge(age); 29 30 System.out.println("当前线程 : " + Thread.currentThread().getName() + " 修改对象内容为: " 31 + this.getName() + ", " + this.getAge()); 32 33 Thread.sleep(2000); 34 System.out.println("当前线程 : " + Thread.currentThread().getName() + " 结束"); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 } 39 40 public static void main(String[] args) { 41 final ModifyLock modifyLock = new ModifyLock(); 42 Thread t1 = new Thread(new Runnable() { 43 @Override 44 public void run() { 45 modifyLock.changeAttributte("张三", 20); 46 } 47 },"t1"); 48 Thread t2 = new Thread(new Runnable() { 49 @Override 50 public void run() { 51 modifyLock.changeAttributte("李四", 21); 52 } 53 },"t2"); 54 55 t1.start(); 56 try { 57 Thread.sleep(100); 58 } catch (InterruptedException e) { 59 e.printStackTrace(); 60 } 61 t2.start(); 62 } 63 64 }