多執行緒安全修改全域性變數
一步一步分析問題先看下面
1.當我開1000個執行緒出售1000張票時,資料正常,但是當我開1100個時就會出現負數?為什麼啊?明明已經加了判斷
count>0
2.分析如下public class C{ int count=1000;//火車票 static long currenttime; static long endtime; static File file=new File("C:/Users/絕影/Desktop/log.txt"); static OutputStream out=null; static OutputStreamWriter output=null; static { System.out.println("我是靜態塊"); try { out=new FileOutputStream(file); output=new OutputStreamWriter(out); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void one() throws IOException { // TODO 執行緒輸出方法 try { if(count>0) { synchronized(this) { output.write("車票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n"); System.out.println("車票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n"); } }else{ endtime=System.currentTimeMillis(); System.out.println(endtime); System.out.println("抱歉現在沒有車票了"+"總共用了時間"+(endtime-currenttime)+"賣完完餘票"); System.exit(0); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { output.flush(); } } public static void main(String[] args) throws InterruptedException { currenttime=System.currentTimeMillis(); final C output = new C(); final R b = new R(); final BB e = new BB(); System.out.println("當前時間"+currenttime); //方式1 for(int j=1;j<=1100;j++) { new Thread() { public void run() { try { try { sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }//模仿網路延遲 output.one(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } } }
if(count>0) {
synchronized(this) {
output.write("車票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("車票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
}
分析應該是這樣的,雖然加了synchronized保證了同一個時刻當出現多個執行緒搶佔時,這時候只有一個執行緒能進入到synchronized塊中,但是對於if(count>0) 判斷其它執行緒還是可以訪問。因為我電腦時雙核四執行緒,
雙核四執行緒是每個核心不停地在兩個執行緒之間切換,以達到和四核相似的處理能力,自然同一個時刻可以跑兩個執行緒。
如果我們假設某一時刻記憶體中的數count是1,執行類似i--操作,比方第1000個執行緒進行了if(count>0)後運行同步塊中的內容,這時候第1001個執行緒已經拷貝了之前記憶體中變數1的副本進行了if(count>0)操作,遇到了 synchronized發現有執行緒在使用,此時等待,當第1000個執行緒執行完同步副本變數到記憶體時,此時count=0,然後第1001個執行緒已經用之前的變數副本1進行了count>0的判斷,所以還是進入了同步塊,自然就出現了負數問題。為了驗證假設當我在同步快中再次執行if(count>0)操作後在操作變數後,count不在出現負數,說明假設成立,也說明我們執行緒在使用到變數時,每次都會去讀取當前記憶體中的當前變數內容。多執行緒不同步出現執行緒共享變數安全是因為每次讀取的記憶體中內容不是最新更新的一個才導致。
其實單核多執行緒我覺得也會出現,當第1000個執行了if(count>0)的判斷後讓出了cpu,切換到了第1001個執行,第1001個執行完判斷並且執行完同步塊中的內容更新記憶體中的count為0,但是第1000個執行緒已經執行過if(count>0),所以不會再執行這個判斷,會接著往後執行,所以如果同步塊中不加判斷還是會出現負數,這個有點類似於單例模式的這種模型,為了在多執行緒下保證單例
- publicclass Test18 {
- static Object lock=new Object();
- privatestatic Test18 singleton=null;
- private Test18 (){}
- publicstatic Test18 getInstance(){
- if(singleton==null){
- synchronized (lock) {
- if(singleton==null)
-
singleton=new
- }
- }
- return singleton;
- }
- }
所以修改同步塊內容為
synchronized(this) {
if(count>0) {
output.write("車票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("車票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
}
二:用lock達到同樣效果
class R extends Thread{
static int count=1000;//火車票
static long currenttime;
static long endtime;
int agent=0;
static File file=new File("C:/Users/絕影/Desktop/log.txt");
static OutputStream out=null;
static OutputStreamWriter output=null;
private Lock lock = new ReentrantLock();// 鎖物件
static {
System.out.println("我是靜態塊");
try {
out=new FileOutputStream(file);
output=new OutputStreamWriter(out);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void one() throws IOException {
// TODO 執行緒輸出方法
try {
if(count>0) {
lock.lock();// 得到鎖
if(count>0) {
output.write("車票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("車票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
lock.unlock();// 釋放鎖
}else{
endtime=System.currentTimeMillis();
System.out.println(endtime);
System.out.println("抱歉現在沒有車票了"+"總共用了時間"+(endtime-currenttime)+"賣完完餘票");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.flush();
}
}
@Override
public void run() {
try {
try {
sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
one();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class C {
int count = 1000;// 火車票
static long currenttime;
static long endtime;
public static void main(String[] args) throws InterruptedException {
currenttime = System.currentTimeMillis();
final R b = new R();
System.out.println("當前時間" + currenttime);
// 方式二
for (int j = 1; j <= 1200; j++) {// 1000
new Thread(b).start();
}
}
}
方法三:synchronized關鍵字加到方法上,這種只是鎖的範圍大,效率低
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
class BB implements Runnable{
static int count=1000;//火車票
static long currenttime;
static long endtime;
int agent=0;
static File file=new File("C:/Users/絕影/Desktop/log.txt");
static OutputStream out=null;
static OutputStreamWriter output=null;
static {
System.out.println("我是靜態塊");
try {
out=new FileOutputStream(file);
output=new OutputStreamWriter(out);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void one() throws IOException {
// TODO 執行緒輸出方法
try {
if(count>0) {
output.write("車票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("車票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}else{
endtime=System.currentTimeMillis();
System.out.println(endtime);
System.out.println("抱歉現在沒有車票了"+"總共用了時間"+(endtime-currenttime)+"賣完完餘票");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.flush();
}
}
@Override
public void run() {
try {
one();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class C{
static long currenttime;
static long endtime;
public static void main(String[] args) throws InterruptedException {
currenttime=System.currentTimeMillis();
final BB e = new BB();
System.out.println("當前時間"+currenttime);
//方式三
for(int j=1;j<=1200;j++) {//1000
new Thread(e).start();
}
}
}