synchronized與單例的線程安全問題
如果對synchronized
瞭解不夠深入,建議先理解透徹下面這張圖
遇到的問題
單例中多個方法操作同一變量,出現數據混亂/線程不安全問題。
原先的代碼是synchronized加錯了地方,或者理解不透測。
synchronized
線程同步問題大都使用synchronized解決,有同步代碼塊和同步方法的兩種方式。
代碼示例
錯誤示例一
Single.java
public class Single {
private final String TAG = "Single";
private static Single instance = null;
private int num = 0;
public static synchronized Single getInstance() {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
return instance;
}
public void addNum() {
num++;
Log.e(TAG, "num:" + num);
}
public void reduceNum() {
num--;
Log.e(TAG, "num:" + num);
}
}
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void ClickButton(View view) {
for (int i = 0; i < 1000; i++) {
ThreadPool.getInstance().execute(new Runnable() {
@Override
public void run() {
Single.getInstance().addNum();
Single.getInstance().reduceNum();
}
});
}
}
}
結果:打印num,發現出現很多次num:2的情況。線程不安全。
錯誤示例二
在addNum
與reduceNum
方法前加上synchronized
結果:打印num,發現還是會出現num:2的情況。線程不安全。
問題原因及解決
造成上面問題的原因是:即使有線程安全的單例,也僅僅只能保證單例有一個,並不能保證單例中的方法不會出現線程安全問題。因爲當多個線程獲取到該單例對象後,其中的方法就有可能不同步。
錯誤示例二中的方法僅僅只對方法做了同步,並不同保證加減統一。
解決辦法有兩種:
第一種,在線程中加上同步代碼塊。
第二種,在單例代碼中增加一個同步方法,將加減操作放在這個同步方法中。
正確示例一
添加同步代碼塊
Single.java
public class Single {
private final String TAG = "Single";
private static Single instance = null;
private int num = 0;
public static synchronized Single getInstance() {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
return instance;
}
public void addNum() {
num++;
Log.e(TAG, "num:" + num);
}
public void reduceNum() {
num--;
Log.e(TAG, "num:" + num);
}
}
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void ClickButton(View view) {
for (int i = 0; i < 1000; i++) {
ThreadPool.getInstance().execute(new Runnable() {
@Override
public void run() {
synchronized ("") { //添加同步代碼塊
Single.getInstance().addNum();
Single.getInstance().reduceNum();
}
}
});
}
}
}
正確示例二
增加新的同步方法,將加減方法放在該方法中調用
Single.java
public class Single {
private final String TAG = "Single";
private static Single instance = null;
private int num = 0;
public static synchronized Single getInstance() {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
return instance;
}
public synchronized void cal() {
addNum();
reduceNum();
}
private void addNum() {
num++;
Log.e(TAG, "num:" + num);
}
private void reduceNum() {
num--;
Log.e(TAG, "num:" + num);
}
}
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void ClickButton(View view) {
for (int i = 0; i < 1000; i++) {
ThreadPool.getInstance().execute(new Runnable() {
@Override
public void run() {
Single.getInstance().cal(); //調用同步方法
}
});
}
}
}