深入理解java多執行緒(六)
關於java多執行緒的概念以及基本用法:java多執行緒基礎
6,單例模式與多執行緒
如何使單例模式遇到多執行緒是安全的這是下面要討論的內容
6.1,立即載入
立即載入就是在使用類的時候已經將物件建立完畢,例如String s = new String(”hello world!”);這裡就是直接將物件例項化了
MyObject類:
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject () {
}
public static MyObject getInstance() {
return myObject;
}
}
測試類:
public class Run extends Thread{
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
public static void main(String[] args) {
Run run1 = new Run();
Run run2 = new Run();
Run run3 = new Run();
run1.start();
run2.start();
run3.start();
}
}
結果:
1141021789
1141021789
1141021789
列印的hashCode是同一個值,說明物件時同一個
6.2,延遲載入
延遲載入就是在呼叫get()方法時例項才被建立,常見的是在
get()方法中進行new例項化
MyObject類:
public class MyObject{
public static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
if(myObject != null) {
}else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject = new MyObject();
}
return myObject;
}
}
測試類:
public class Run extends Thread{
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
public static void main(String[] args) {
Run run1 = new Run();
Run run2 = new Run();
Run run3 = new Run();
run1.start();
run2.start();
run3.start();
}
}
結果:
1796851831
141757079
1721087309
延遲載入顯然不是單例,那麼如何讓延遲載入變成單例呢,可以用synchronized關鍵字和同步程式碼塊
1,延遲載入同步方案–synchronized關鍵字
上面的程式碼不變,
public static MyObject getInstance()->synchronized public static MyObject getInstance()
執行結果顯示hashcode值都是一樣的,所以這種方法是可行的
2,延遲載入同步方案–同步程式碼塊
public static MyObject getInstance()改為:
public static MyObject getInstance() {
synchronized(MyObject.class) {
if(myObject != null) {
}else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject = new MyObject();
}
}
return myObject;
}
這樣做也可以實現單例,但是這兩種方法都有一個問題就是,效率比較低,都把整個方法給鎖住了,下面嘗試對部分程式碼同步
3,延遲載入同步方案–針對重要程式碼單獨同步
public static MyObject getInstance()改為:
public static MyObject getInstance() {
if(myObject != null) {
}else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//部分程式碼被上鎖,但存在非執行緒安全問題
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
return myObject;
}
結果:
1721087309
141757079
1796851831
對部分程式碼實現同步,執行效率得到了提高,但是單例卻實現不了了
4,延遲載入同步方案–使用DCL雙檢查鎖機制
DCL雙檢查鎖機制是實現多執行緒環境中延遲載入單例設計模式
public class MyObject{
private volatile static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
if(myObject != null) {
}else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
//部分程式碼被上鎖,但存在非執行緒安全問題
synchronized (MyObject.class) {
if(myObject ==null) {
myObject = new MyObject();
}
}
}
return myObject;
}
}
結果:
141757079
141757079
141757079
實現了單例
4,延遲載入同步方案–靜態內部類
之前做一個安卓-RFID讀卡器實現打卡的App,當時做到資料獲取時,老是拿不到資料,因為當時的程式並不是單例,而用靜態內部類可以很好的實現,單例模式
public class Myobject{
private static class MyObjectHandler{
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
}
結果當然是可以實現單例的
5,序列化與反序列化的單例模式實現
靜態內建類可以實現執行緒安全,但是如果遇到了序列化物件,使用預設的方式執行的結果則會是多例
新建MyObject類:
public class MyObject implements Serializable{
private static final long serialVersionUID = 888L;
//內部類方式
private static class MyObjectHandler{
private static final MyObject myObject = new MyObject();
}
private MyObject() {}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
/*protected Object readResolve() throws ObjectStreamException{
System.out.println("111");
return MyObjectHandler.myObject;
}*/
}
SaveAndRead類:
public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef =
new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
try {
FileInputStream fisRef =
new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e1) {
e1.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
}
}
這樣執行得到的結果不是同一個物件,解決辦法就是去掉註釋使用readResolve()方法
6,使用static程式碼塊實現單例模式
由於靜態程式碼塊是在使用類時就執行了的,所以藉助這一特性,可以實現單例
新建MyObject類:
public class MyObject {
private static MyObject instance = null;
private MyObject() {}
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
測試類:
public class Run extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
public static void main(String[] args) {
Run run1 = new Run();
Run run2 = new Run();
Run run3 = new Run();
run1.start();
run2.start();
run3.start();
}
}
結果:
1721087309
1721087309
1721087309
。。。。
。。。。
。。。。
7,使用enum列舉資料型別實現單例模式
新建MyObject類:
public class MyObject {
private enum MyEnumSingleton{
INSTANCE;
private Resource resource;
private MyEnumSingleton(){
resource = new Resource();
}
public Resource getResource(){
return resource;
}
}
public static Resource getResource(){
return MyEnumSingleton.INSTANCE.getResource();
}
}
測試類:
public class Run {
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.getResource().hashCode());
}
}
}
public static void main(String[] args) {
Run.MyThread t1 = new Run().new MyThread();
Run.MyThread t2 = new Run().new MyThread();
Run.MyThread t3 = new Run().new MyThread();
t1.start();
t2.start();
t3.start();
}
}
結果當然是單例啦