深入理解synchronized关键字的用法

 内置锁:每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。

互斥锁:内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

synchronized既是内置锁也是互斥锁

synchronized三种修饰

第一、修饰普通方法

代码示例:

代码示例:代码

  1. public class TestSynchronized {  
  2.       
  3.     public synchronized void out() throws InterruptedException {  
  4.       System.out.println("test开始..");    
  5.       try {    
  6.           Thread.sleep(5000L);    
  7.       } catch (InterruptedException e) {    
  8.           e.printStackTrace();    
  9.       }    
  10.       System.out.println("test结束..");  
  11.     }  
  12.       
  13.     public static void main(String[] args) {  
  14.         TestSynchronized testSync = new TestSynchronized();  
  15.         TestSynchronized testSync2 = new TestSynchronized();  
  16.           
  17.         new Thread(() -> {  
  18.             try {  
  19.                 testSync.out();  
  20.             } catch (InterruptedException e) {  
  21.                 e.printStackTrace();  
  22.             }  
  23.         }).start();  
  24.           
  25.         new Thread(() -> {  
  26.             try {  
  27.                 testSync2.out();  
  28.             } catch (InterruptedException e) {  
  29.                 e.printStackTrace();  
  30.             }  
  31.         }).start();  
  32.           
  33.     }  
  34.   
  35. }  

 控制台输出:Java代码

  1. test开始..  
  2. test开始..  
  3. test结束..  
  4. test结束..  

 很明显,synchronized修饰普通方法的时候,锁住的是对象的实例,代码示例中,testSync 和testSync2分别都是TestSynchronized对象的实例,他们两个都可以同时进入synchronized修饰的普通方法,所以得出,synchronized修饰普通方法的时候,锁住的是对象的实例。

第二、修饰静态方法

代码示例:

Java代码

  1. public class TestSynchronized {  
  2.   
  3.         public static synchronized void staticOut() throws InterruptedException {  
  4.         long startTime = System.currentTimeMillis();    
  5.         System.out.println("test开始..");    
  6.           try {    
  7.               Thread.sleep(5000L);    
  8.           } catch (InterruptedException e) {    
  9.               e.printStackTrace();    
  10.           }    
  11.           System.out.println("test结束..");  
  12.           long endTime = System.currentTimeMillis();  
  13.           System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  14.         }  
  15.       
  16.     public static void main(String[] args) {  
  17.         TestSynchronized testSync = new TestSynchronized();  
  18.         TestSynchronized testSync2 = new TestSynchronized();  
  19.           
  20.         new Thread(() -> {  
  21.             try {  
  22.                 testSync.staticOut();  
  23.             } catch (InterruptedException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.         }).start();  
  27.           
  28.         new Thread(() -> {  
  29.             try {  
  30.                 testSync2.staticOut();  
  31.             } catch (InterruptedException e) {  
  32.                 e.printStackTrace();  
  33.             }  
  34.         }).start();  
  35.           
  36.     }  
  37.   
  38. }  

 控制台输出:Java代码

  1. test开始..  
  2. test结束..  
  3. 线程Thread-0程序运行时间:5000ms  
  4. test开始..  
  5. test结束..  
  6. 线程Thread-1程序运行时间:5000ms  

 可以看出,synchronized修饰静态方法的时候,起到了锁的作用,线程分别获得锁后才进入静态方法中,但是尽量不要使用synchronized修饰静态方法,因为它锁住的是整个类,也就是说,在整个类中的其他synchronized修饰的方法都会被锁住。

示例代码如下:

Java代码

  1. public class TestSynchronized {  
  2.      
  3.     public static synchronized void staticOut() throws InterruptedException {  
  4.         long startTime = System.currentTimeMillis();    
  5.         System.out.println("test开始..");    
  6.           try {    
  7.               Thread.sleep(5000L);    
  8.           } catch (InterruptedException e) {    
  9.               e.printStackTrace();    
  10.           }    
  11.           System.out.println("test结束..");  
  12.           long endTime = System.currentTimeMillis();  
  13.           System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  14.     }  
  15.       
  16.       
  17.     public static synchronized void staticOut2() throws InterruptedException {  
  18.         long startTime = System.currentTimeMillis();    
  19.         System.out.println("test2开始..");    
  20.           try {    
  21.               Thread.sleep(5000L);    
  22.           } catch (InterruptedException e) {    
  23.               e.printStackTrace();    
  24.           }    
  25.           System.out.println("test2结束..");  
  26.           long endTime = System.currentTimeMillis();  
  27.           System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  28.     }  
  29.       
  30.     public static void main(String[] args) {  
  31.         TestSynchronized testSync = new TestSynchronized();  
  32.         TestSynchronized testSync2 = new TestSynchronized();  
  33.           
  34.         new Thread(() -> {  
  35.             try {  
  36.                 testSync.staticOut();  
  37.             } catch (InterruptedException e) {  
  38.                 e.printStackTrace();  
  39.             }  
  40.         }).start();  
  41.           
  42.         new Thread(() -> {  
  43.             try {  
  44.                 testSync2.staticOut2();  
  45.             } catch (InterruptedException e) {  
  46.                 e.printStackTrace();  
  47.             }  
  48.         }).start();  
  49.           
  50.     }  
  51.   
  52. }  

 控制台输出:Java代码

  1. test开始..  
  2. test结束..  
  3. 线程Thread-0程序运行时间:5000ms  
  4. test2开始..  
  5. test2结束..  
  6. 线程Thread-1程序运行时间:5001ms  

 可以看出,线程Thread-0进入synchronized修饰的静态方法staticOut()的时候,这个类就被锁住了,线程Thread-1无法获得锁,只能等待锁的释放后才能进入方法staticOut2()。所以使用synchronized修饰静态方法需要慎重。

第三、修饰代码块

示例代码:

Java代码

  1. public class TestSynchronized {  
  2.   
  3.     private Object lock = new Object();  
  4.     public void lockOut(){  
  5.         synchronized(lock){  
  6.             long startTime = System.currentTimeMillis();    
  7.             System.out.println("test开始..");    
  8.             try {    
  9.                 Thread.sleep(5000L);    
  10.             } catch (InterruptedException e) {    
  11.                 e.printStackTrace();    
  12.             }    
  13.             System.out.println("test结束..");  
  14.             long endTime = System.currentTimeMillis();  
  15.             System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  16.               
  17.         }  
  18.     }  
  19.       
  20.     public static void main(String[] args) {  
  21.         TestSynchronized testSync = new TestSynchronized();  
  22.         TestSynchronized testSync2 = new TestSynchronized();  
  23.           
  24.         new Thread(() -> {  
  25.             testSync.lockOut();  
  26.         }).start();  
  27.           
  28.         new Thread(() -> {  
  29.             testSync2.lockOut();  
  30.         }).start();  
  31.           
  32.     }  
  33. }  

 控制台输出:Java代码

  1. test开始..  
  2. test开始..  
  3. test结束..  
  4. test结束..  
  5. 线程Thread-0程序运行时间:5000ms  
  6. 线程Thread-1程序运行时间:5000ms  

 synchronized修饰代码块时,锁住的是一个对象 synchronized (lock) 即synchronized后面括号里的内容,因为两个对象创建了两个不同对象实例lock,所以两个对象的线程都可以同时进入synchronized修饰代码块。如果想锁住synchronized修饰的代码块,只需要确定synchronized后面括号里锁住同一

对象即可,常用的方法如下:

1、synchronized锁这个类对应的Class对象。

实例代码:

Java代码

  1. public class TestSynchronized {  
  2.       
  3. //  private Object lock = new Object();  
  4.     public void lockOut(){  
  5.         synchronized(TestSynchronized.class){  
  6.             long startTime = System.currentTimeMillis();    
  7.             System.out.println("test开始..");    
  8.             try {    
  9.                 Thread.sleep(5000L);    
  10.             } catch (InterruptedException e) {    
  11.                 e.printStackTrace();    
  12.             }    
  13.             System.out.println("test结束..");  
  14.             long endTime = System.currentTimeMillis();  
  15.             System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  16.               
  17.         }  
  18.     }  
  19.       
  20.     public static void main(String[] args) {  
  21.         TestSynchronized testSync = new TestSynchronized();  
  22.         TestSynchronized testSync2 = new TestSynchronized();  
  23.           
  24.         new Thread(() -> {  
  25.             testSync.lockOut();  
  26.         }).start();  
  27.           
  28.         new Thread(() -> {  
  29.             testSync2.lockOut();  
  30.         }).start();  
  31.           
  32.     }  
  33.   
  34. }  

 控制台输出:Java代码

  1. test开始..  
  2. test结束..  
  3. 线程Thread-0程序运行时间:5001ms  
  4. test开始..  
  5. test结束..  
  6. 线程Thread-1程序运行时间:5002ms  

 让synchronized锁这个类对应的Class对象这种方法实现了全局锁的效果,和synchronized修饰静态方法一样(static synchronized方法也是相当于全局锁),整个类就被锁住了,所以此方法一样需要慎重使用。

2、创建一个单例对象,锁住的是该单例对象,单例对象只有一个实例。

Java代码

  1. public class TestSynchronized {  
  2.    
  3.     private volatile static Object lock = new Object();   
  4.     public void lockOut(){  
  5.         synchronized(lock){  
  6.                         System.out.println("指针地址:" + lock.toString());    
  7.             long startTime = System.currentTimeMillis();    
  8.             System.out.println("test开始..");    
  9.             try {    
  10.                 Thread.sleep(5000L);    
  11.             } catch (InterruptedException e) {    
  12.                 e.printStackTrace();    
  13.             }    
  14.             System.out.println("test结束..");  
  15.             long endTime = System.currentTimeMillis();  
  16.             System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  17.               
  18.         }  
  19.     }  
  20.       
  21.     public static void main(String[] args) {  
  22.         TestSynchronized testSync = new TestSynchronized();  
  23.         TestSynchronized testSync2 = new TestSynchronized();  
  24.           
  25.         new Thread(() -> {  
  26.             testSync.lockOut();  
  27.         }).start();  
  28.           
  29.         new Thread(() -> {  
  30.             testSync2.lockOut();  
  31.         }).start();  
  32.           
  33.     }  
  34.   
  35. }  

 控制台输出:Java代码

  1. 指针地址:java.lang.Object@ce407e7  
  2. test开始..  
  3. test结束..  
  4. 线程Thread-0程序运行时间:1000ms  
  5. 指针地址:java.lang.Object@ce407e7  
  6. test开始..  
  7. test结束..  
  8. 线程Thread-1程序运行时间:1001ms  

 保证了单例对象lock 的实例唯一性,synchronized锁住同一个固定对象,从控制台上可以看出,访问代码块的对象指针地址是一样的。

3、访问该代码块的对象唯一

示例代码:Java代码

  1. public class TestSynchronized {  
  2.       
  3. //  private volatile static Object lock = new Object();   
  4.       
  5.     public void lockOut(){  
  6.         synchronized(this){  
  7.                         System.out.println("指针地址:" + this.toString());    
  8.             long startTime = System.currentTimeMillis();    
  9.             System.out.println("test开始..");    
  10.             try {    
  11.                 Thread.sleep(1000);    
  12.             } catch (InterruptedException e) {    
  13.                 e.printStackTrace();    
  14.             }    
  15.             System.out.println("test结束..");  
  16.             long endTime = System.currentTimeMillis();  
  17.             System.out.println("线程"+Thread.currentThread().getName() + "程序运行时间:" + (endTime - startTime) + "ms");  
  18.               
  19.         }  
  20.     }  
  21.       
  22.     public static void main(String[] args) {  
  23.         TestSynchronized testSync = new TestSynchronized();  
  24.           
  25.             new Thread(() -> {  
  26.                 testSync.lockOut();  
  27.             }).start();  
  28.               
  29.             new Thread(() -> {  
  30.                 testSync.lockOut();  
  31.             }).start();  
  32.         }  
  33.   
  34. }  

 控制台输出:Java代码

  1. 指针地址:com.test.test.TestSynchronized@1dca18a4  
  2. test开始..  
  3. test结束..  
  4. 线程Thread-0程序运行时间:1000ms  
  5. 指针地址:com.test.test.TestSynchronized@1dca18a4  
  6. test开始..  
  7. test结束..  
  8. 线程Thread-1程序运行时间:1000ms  

 synchronized后面括号的this指的是访问该代码块的对象,从控制台上可以看出,访问代码块的对象指针地址是一样的,从而可以得出他们是固定同一对象。

猜你喜欢

转载自blog.csdn.net/leonardc/article/details/89025835