線程的互斥
多線程操作同一實例的問題
在多線程環(huán)境中,經(jīng)常有兩個以上線程操作同一實例的問題,無論是并行Parallel環(huán)境還是并發(fā)Concurrent環(huán)境,都有發(fā)生有多個線程修改同一變量的問題,如果這個變量是成員變量,多線程將會給程序帶來破壞性的影響。請見以下代碼。
資源庫類
public class ResourceLib {
private long count1;
private long count2;
public ResourceLib(int count) {
this.count1 = count;
this.count2 = count;
}
/**
* 取回資源
* 加上synchronized才是線程安全
*
* @param count
*/
public void fetch(int count) {
count1 += count;
mockLongTimeProcess();
count2 += count;
checkTwoCount(count);
}
/**
* 送出資源
* 加上synchronized才是線程安全
*
* @param count
* @return
*/
public void send(int count) {
count1 -= count;
mockLongTimeProcess();
count2 -= count;
checkTwoCount(count);
}
/**
* 模擬一個耗時過程
*
*/
private void mockLongTimeProcess(){
try{
Thread.sleep(1000);
}
catch(Exception ex){
ex.printStackTrace();
}
}
private void checkTwoCount(int borrowCount) {
if (count1 != count2) {
System.out.println(count1 + "!= " + count2);
System.exit(0);
} else {
System.out.println(count1 + "==" + count2);
}
if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
count1 = 0;
count2 = 0;
}
}
public static void main(String[] args) {
ResourceLib lib = new ResourceLib(10000);
for (int i = 1; i < 20; i++) {
new Supplier(String.valueOf(i), i, lib);
}
for (int i = 1; i < 10; i++) {
new Comsumer(String.valueOf(i), i, lib);
}
}
}
取資源和給資源的兩個線程
public class Comsumer implements Runnable{
private ResourceLib resourceLib;
private int count;
public Comsumer(String name,int count,ResourceLib resourceLib){
this.count=count;
this.resourceLib=resourceLib;
Thread thread=new Thread(this);
thread.start();
}
public void run(){
while(true){
resourceLib.send(count);
}
}
}
public class Supplier implements Runnable{
private ResourceLib resourceLib;
private int count;
public Supplier(String name,int count,ResourceLib resourceLib){
this.count=count;
this.resourceLib=resourceLib;
Thread thread=new Thread(this);
thread.start();
}
public void run(){
while(true){
resourceLib.fetch(count);
}
}
}
運行結(jié)果
在main函數(shù)中,程序啟動了多個消費者線程和生產(chǎn)者線程,消費者線程在不斷減少count1和count2;生產(chǎn)者線程在不斷增加count1和count2,在單線程環(huán)境中,程序絕不會出現(xiàn)count1和count2不相等的情況,而多線程環(huán)境中,可能有一個線程在檢查count1和count2時,其中一個已經(jīng)被另一個線程所修改。
因此導致了兩個值不相等的情況發(fā)生。
運行結(jié)果之一
10145!= 10001
10145!= 10003
10145!= 10006
10145!= 10010
10145!= 10015
10145!= 10021
10145!= 10028
10145!= 10036
10145!= 10045
10145!= 10055
10145!= 10066
另一個經(jīng)典多線程實例:銀行取款
package com.sitinspring.unsafebank;
public class Bank{
private int count;
public Bank(int count){
this.count=count;
}
public void withdraw(int money){
if(count>money){
mockLongTimeProcess();// 模擬耗時過程
count-=money;
System.out.println("提走"+money+" 現(xiàn)有"+count);
}
else{
System.out.println(" 現(xiàn)有數(shù)量"+count+"小于"+money+" 不能提取");
}
checkCount();
}
public void checkCount(){
if(count<0){
System.out.println(count + "< 0 ");
System.exit(0);
}
}
/**
* 模擬一個耗時過程
*
*/
private void mockLongTimeProcess(){
try{
Thread.sleep(1000);
}
catch(Exception ex){
ex.printStackTrace();
}
}
public static void main(String[] args){
Bank bank=new Bank(1000);
for(int i=1;i<10;i++){
new Customer(i*i*i,bank);
}
}
}
客戶類及講述
public class Customer implements Runnable{
private Bank bank;
private int count;
public Customer(int count,Bank bank){
this.count=count;
this.bank=bank;
Thread thread=new Thread(this);
thread.start();
}
public void run(){
while(true){
bank.withdraw(count);
}
}
}
在單線程環(huán)境中,提款時銀行的總數(shù)絕不會是負數(shù),但在多線程環(huán)境中,有可能在一個線程A符合條件在進行耗時運算和網(wǎng)絡(luò)數(shù)據(jù)傳遞時,另一個線程B已經(jīng)把錢提走,總數(shù)已經(jīng)發(fā)生變化,結(jié)果A線程再提款時總錢數(shù)已經(jīng)減小了,因此致使銀行總錢數(shù)小于零。
解決方法:在對成員變量進行修改的函數(shù)前加上synchronized關(guān)鍵字
synchronized方法又被成為”同步“方法。當一個方法加上關(guān)鍵字synchronized聲明之后,就可以讓一個線程操作這個方法。“讓一個線程操作”并不是說只能讓某一個特定的線程操作而已,而是指一次只能讓一個線程執(zhí)行,也就是說,在一個線程沒有退出同步方法前,其它線程絕無可能進入這個同步方法和其它并列的同步方法,只能在外面排隊等候。
一個實例的synchronized方法只能允許1次一個線程執(zhí)行。但是非synchronized方法就沒有這個限制,它可以供2個以上的線程執(zhí)行。
修改后的線程安全的Bank類
public class Bank{
private int count;
public Bank(int count){
this.count=count;
}
public synchronized void withdraw(int money){
if(count>money){
mockLongTimeProcess();// 模擬耗時過程
count-=money;
System.out.println("提走"+money+" 現(xiàn)有"+count);
}
else{
System.out.println(" 現(xiàn)有數(shù)量"+count+"小于"+money+" 不能提取");
}
checkCount();
}
public void checkCount(){
if(count<0){
System.out.println(count + "< 0 ");
System.exit(0);
}
}
。。。、// 部分代碼省略
}
修改后的線程安全的ResourceLib類
public class ResourceLib {
private long count1;
private long count2;
public synchronized void fetch(int count) {
count1 += count;
mockLongTimeProcess();
count2 += count;
checkTwoCount(count);
}
public synchronized void send(int count) {
count1 -= count;
mockLongTimeProcess();
count2 -= count;
checkTwoCount(count);
}
public void checkTwoCount(int borrowCount) {
if (count1 != count2) {
System.out.println(count1 + "!= " + count2);
System.exit(0);
} else {
System.out.println(count1 + "==" + count2);
}
if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
count1 = 0;
count2 = 0;
}
}
}
注:部分代碼省略
執(zhí)行之后
在一個執(zhí)行synchronized方法的線程執(zhí)行結(jié)束后,鎖定即被釋放, 其它不得其門而入的線程開始爭搶鎖定,一定會有一個線程獲取鎖定,沒有搶到的線程只好再繼續(xù)等候.
注意: 非靜態(tài)的synchronized方法鎖定的對象是實例,靜態(tài)的synchronized方法鎖定的對象是類對象。
同步塊
以下同步方法可用右邊的同步塊代替:
public synchronized void fun(){
………
}
與左邊同步方法對等的同步塊:
public void fun(){
synchronized(this){
………
}
}
同步塊和同步方法的比較
1)同步方法鎖定的類的實例或類對象,同步塊則可以換成任意實例,靈活性更高。
2)有時需要多個鎖定而不是一個,如函數(shù)A和函數(shù)B需要鎖定1,函數(shù)B和函數(shù)C需要鎖定2,這時如果使用同步方法無疑會鎖定A和C,造成程序效率的降低。這時最應(yīng)該使用同步塊。
什么時候該加同步synchronized
如果一個函數(shù)或代碼塊有可能被多個線程進入,而這個函數(shù)或代碼塊又修改了類的成員變量,則這個這個函數(shù)或代碼塊就應(yīng)該加上同步synchronized。
如果一個函數(shù)或代碼有可能被多個線程進入,而這個函數(shù)或代碼塊只是讀取類的成員變量,則這個這個函數(shù)或代碼塊就不該加上同步synchronized。
posted on 2008-02-22 12:43 和風細雨 閱讀(482) 評論(1) 編輯 收藏 所屬分類: 線程