Java併發原理學習筆記+總結+實戰(3)——單例與線程安全問題

1.單例模式

餓漢模式

public class Singleton {

  // 私有化構造方法
  private Singleton() {
  }

  private static Singleton instance = new Singleton();

  public static Singleton getInstance() {
    return instance;
  }
}

懶漢模式

public class Singleton2 {
	//1.將構造方式私有化,不允許外邊直接創建對象
	private Singleton2(){
	}
	
	//2.聲明類的唯一實例,使用private static修飾
	private static Singleton2 instance;
	
	//3.提供一個用於獲取實例的方法,使用public static修飾
	public static Singleton2 getInstance(){
		if(instance==null){
			instance=new Singleton2();
		}
		return instance;
	}

單例模式詳細情況在這裏就不在贅述了

1.1線程安全性

    出現線程安全性問題的前提條件必須有:1、多線程的環境下;2、必須有共享資源;3、對資源進行非原子性操作。

    在餓漢模式中,因爲對象的創建是在初始化類的時候就進行並外部無法直接訪問其構造函數,所以使用餓漢模式的時候,在多線程模式下是安全的。

    但是在懶漢模式中,我們保證該類是單例的關鍵代碼是

if(instance == null)

    並且java中new是不具備原子性的(主要涉及到賦值問題),所以,在併發環境下,是可能會執行多次new操作的。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadMain {

  public static void main(String[] args) {

    ExecutorService threadPool = Executors.newFixedThreadPool(20);

    for (int i = 0; i < 20; i++) {
      threadPool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + ":" + Singleton2.getInstance());
        }
      });
    }

  }

}

爲了使結果更清晰,將懶漢模式稍微改造一下

if(instance==null){
    try{
       Thread.sleep(100);
     }catch(Exception e){
       e.printStackTrace();
  }
	instance=new Singleton2();	
}

結果很明顯,幾個線程創建了好多個不同的實例對象

 因此,我們可以:

1)、通過添加synchronize來修飾getInstance()方法,簡單粗暴,但是簡單粗暴同時也意味着要付出慘重的效率代價。當一個線程成功獲取鎖進入方法後,其他線程就需要等待鎖的釋放,在等待的過程中就是自旋,自旋是非常消耗CPU資源的。

1.1-1什麼是自旋以及理解自旋鎖、死鎖和重入鎖:

  • 重入鎖

        synchronize就是典型的重入鎖,就是說鎖只能被一個線程持有,會把其他線程擋在外面。當兩個不同方法A、B都是使用同一個對象去上鎖的,其中A方法內部調用B方法,那麼,當某一個線程成功獲取A方法的鎖之後,該線程也能直接訪問B方法。下面是例子

public class Demo {


  public synchronized void a() {
    System.out.println("a");
    b();
  }

  public synchronized void b() {
    System.out.println("b");
  }
}

 那麼,當在多個線程A方法和B方法分別調用的時候,結果也是相同的

public class Demo {


  public synchronized void a() {
    System.out.println("a");

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public synchronized void b() {
    System.out.println("b");

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

  }

  public static void main(String[] args) {
    Demo d1 = new Demo();
 

    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.b();
      }
    }).start();
  }

}

 但是,當程序修改爲實例化兩個對象的時候,synchronize是鎖不住的,因爲這是兩個不同的線程了,所以,只有當想成拿到同一個的對象的時候,才能進行鎖重入的

public static void main(String[] args) {
    Demo d1 = new Demo();
    Demo d2 = new Demo();

    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d2.b();
      }
    }).start();
  }

1.1)、雙重檢查加鎖

    if (instance == null) {
      synchronized (Singleton2.class) {
        if (instance == null) {
          instance = new Singleton2();  //指令重排序
        }
      }
    }

指令重排序:jvm會在不影響最終結果的前提下,爲了提高程序的執行效率/性能,會對指令的執行順序進行重排序。所以,如果上述代碼在jvm進行指令重排序之後,即當new Singleton2()這句代碼被重排序後先執行,instance的引用就會指向該對象的內存空間地址,導致instance不爲空,那麼,之後執行爲空判斷就會出現問題。

解決辦法,在聲明唯一實例的時候,使用volatile關鍵字進行修飾

	private static volatile Singleton2 instance;
  •  自旋鎖

是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖纔會退出循環。獲取鎖的線程一直處於活躍狀態,但是並沒有執行任何有效的任務。

import java.util.Random;

/**
 * 多個線程執行完畢之後,打印一句話,結束
 *
 * @author worker
 */
public class Demo2 {

  public static void main(String[] args) {

    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程執行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 線程執行完畢了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程執行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 線程執行完畢了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程執行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 線程執行完畢了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程執行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 線程執行完畢了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 線程執行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 線程執行完畢了...");
      }
    }).start();

    while (Thread.activeCount() != 1) {
      // 自旋
    }
    System.out.println("所有的線程執行完畢了...");
  }

}
  • 死鎖
public class Demo3 {

  private Object obj1 = new Object();
  private Object obj2 = new Object();


  public void a() {
    synchronized (obj1) {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized (obj2) {
        System.out.println("a");
      }
    }
  }

  public void b() {
    synchronized (obj2) {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized (obj1) {
        System.out.println("b");
      }
    }
  }

  public static void main(String[] args) {

    Demo3 d = new Demo3();

    new Thread(new Runnable() {

      @Override
      public void run() {
        d.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d.b();
      }
    }).start();
  }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章