synchronized:解決死鎖的問題[轉貼]
最近對
pv
操作研究了一下,才發現原來
java
已經提供了內置的防死鎖功能,不能不說它是很人性的了。下面就是整理的資料:
多線程的互斥與同步
臨界資源問題
前面所提到的線程都是獨立的,而且異步執行,也就是說每個線程都包含了運行時所需要的數據或方法,而不需要外部的資源或方法,也不必關心其它線程的狀態或行為。但是經常有一些同時運行的線程需要共享數據,此時就需考慮其他線程的狀態和行為,否則就不能保證程序的運行結果的正確性。例說明了此問題。
例
class stack{
int idx=0; //
堆棧指針的初始值為
0
char[ ] data = new char[6]; //
堆棧有
6
個字符的空間
public void push(char c){ //
壓棧操作
data[idx] = c; //
數據入棧
idx + +; //
指針向上移動一位
}
public char pop(){ //
出棧操作
idx - -; //
指針向下移動一位
return data[idx]; //
數據出棧
}
}
兩個線程
A
和
B
在同時使用
Stack
的同一個實例對象,
A
正在往堆棧里
push
一個數據,
B
則要從堆棧中
pop
一個數據。如果由於線程
A
和
B
在對
Stack
對象的操作上的不完整性,會導致操作的失敗,具體過程如下所示:
1)
操作之前
data = | p | q | | | | | idx=2
2) A
執行
push
中的第一個語句,將
r
推入堆棧;
data = | p | q | r | | | | idx=2
3) A
還未執行
idx++
語句,
A
的執行被
B
中斷,
B
執行
pop
方法,返回
q
:
data = | p | q | r | | | | idx=1
4
〕
A
繼續執行
push
的第二個語句:
data = | p | q | r | | , | | idx=2
最后的結果相當於
r
沒有入棧。產生這種問題的原因在於對共享數據訪問的操作的不完整性。
互斥鎖
為解決操作的不完整性問題,在
Java
語言中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應於一個可稱為
"
互斥鎖
"
的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
關鍵字
synchronized
來與對象的互斥鎖聯系。當某個對象用
synchronized
修飾時,表明該對象在任一時刻只能由一個線程訪問。
public void push(char c){
synchronized(this){ //this
表示
Stack
的當前對象
data[idx]=c;
idx++;
}
}
public char pop(){
synchronized(this){ //this
表示
Stack
的當前對象
idx--;
return data[idx];
}
}
synchronized
除了象上面講的放在對象前面限制一段代碼的執行外,還可以放在方法聲明中,表示整個方法為同步方法。
public synchronized void push(char c){
…
}
如果
synchronized
用在類聲明中,則表明該類中的所有方法都是
synchronized
的。
多線程的同步
本節將討論如何控制互相交互的線程之間的運行進度
,
即多線程之間的同步問題
,
下面我們將通過多線程同步的模型
:
生產者
-
消費者問題來說明怎樣實現多線程的同步。
我們把系統中使用某類資源的線程稱為消費者,產生或釋放同類資源的線程稱為生產者。
在下面的
Java
的應用程序中,生產者線程向文件中寫數據,消費者從文件中讀數據,這樣,在這個程序中同時運行的兩個線程共享同一個文件資源。通過這個例子我們來了解怎樣使它們同步。
例
class SyncStack{ //
同步堆棧類
private int index = 0; //
堆棧指針初始值為
0
private char []buffer = new char[6]; //
堆棧有
6
個字符的空間
public synchronized void push(char c){ //
加上互斥鎖
while(index = = buffer.length){ //
堆棧已滿,不能壓棧
try{
this.wait(); //
等待,直到有數據出棧
}catch(InterruptedException e){}
}
this.notify(); //
通知其它線程把數據出棧
buffer[index] = c; //
數據入棧
index++; //
指針向上移動
}
public synchronized char pop(){ //
加上互斥鎖
while(index ==0){ //
堆棧無數據,不能出棧
try{
this.wait(); //
等待其它線程把數據入棧
}catch(InterruptedException e){}
}
this.notify(); //
通知其它線程入棧
index- -; //
指針向下移動
return buffer[index]; //
數據出棧
}
}
class Producer implements Runnable{ //
生產者類
SyncStack theStack;
//
生產者類生成的字母都保存到同步堆棧中
public Producer(SyncStack s){
theStack = s;
}
public void run(){
char c;
for(int i=0; i<20; i++){
c =(char)(Math.random()*26+'A');
//
隨機產生
20
個字符
theStack.push(c); //
把字符入棧
System.out.println("Produced: "+c); //
打印字符
try{
Thread.sleep((int)(Math.random()*1000));
/*
每產生一個字符線程就睡眠
*/
}catch(InterruptedException e){}
}
}
}
class Consumer implements Runnable{ //
消費者類
SyncStack theStack;
//
消費者類獲得的字符都來自同步堆棧
public Consumer(SyncStack s){
theStack = s;
}
public void run(){
char c;
for(int i=0;i<20;i++){
c = theStack.pop(); //
從堆棧中讀取字符
System.out.println("Consumed: "+c);
//
打印字符
try{
Thread.sleep((int)(Math.random()*1000));
/*
每讀取一個字符線程就睡眠
*/
}catch(InterruptedException e){}
}
}
}
public class SyncTest{
public static void main(String args[]){
SyncStack stack = new SyncStack();
//
下面的消費者類對象和生產者類對象所操作的是同一個同步堆棧對象
Runnable source=new Producer(stack);
Runnable sink = new Consumer(stack);
Thread t1 = new Thread(source); //
線程實例化
Thread t2 = new Thread(sink); //
線程實例化
t1.start(); //
線程啟動
t2.start(); //
線程啟動
}
}
類
Producer
是生產者模型,其中的
run()
方法中定義了生產者線程所做的操作,循環調用
push()
方法
,
將生產的
20
個字母送入堆棧中,每次執行完
push
操作后,調用
sleep()
方法睡眠一段隨機時間,以給其他線程執行的機會。類
Consumer
是消費者模型,循環調用
pop()
方法
,
從堆棧中取出一個數據,一共取
20
次,每次執行完
pop
操作后,調用
sleep()
方法睡眠一段隨機時間,以給其他線程執行的機會。
程序執行結果
Produced:V
Consumed:V
Produced:E
Consumed:E
Produced:P
Produced:L
...
Consumed:L
Consumed:P
在上述的例子中,通過運用
wait()
和
notify()
方法來實現線程的同步,在同步中還會用到
notifyAll()
方法,一般來說,每個共享對象的互斥鎖存在兩個隊列,一個是鎖等待隊列,另一個是鎖申請隊列,鎖申請隊列中的第一個線程可以對該共享對象進行操作,而鎖等待隊列中的線程在某些情況下將移入到鎖申請隊列。下面比較一下
wait()
、
notify()
和
notifyAll()
方法:
(1) wait,nofity,notifyAll
必須在已經持有鎖的情況下執行
,
所以它們只能出現在
synchronized
作用的范圍內,也就是出現在用
synchronized
修飾的方法或類中。
(2) wait
的作用
:
釋放已持有的鎖
,
進入等待隊列
.
(3) notify
的作用
:
喚醒
wait
隊列中的第一個線程並把它移入鎖申請隊列
.
(4) notifyAll
的作用
:
喚醒
wait
隊列中的所有的線程並把它們移入鎖申請隊列
.
注意
:
1
)
suspend()
和
resume()
在
JDK1.2
中不再使用
suspend()
和
resume(),
其相應功能由
wait()
和
notify()
來實現。
2) stop()
在
JDK1.2
中不再使用
stop(),
而是通過標志位來使程序正常執行完畢。例
6.6
就是一個典型的例子。
例
public class Xyz implements Runnable {
private boolean timeToQuit=false; //
標志位初始值為假
public void run() {
while(!timeToQuit) {//
只要標志位為假,線程繼續運行
…
}
}
public void stopRunning() {
timeToQuit=true;} //
標志位設為真,表示程序正常結束
}
public class ControlThread {
private Runnable r=new Xyz();
private Thread t=new Thread(r);
public void startThread() {
t.start();
}
public void stopThread() {
r.stopRunning(); }
//
通過調用
stopRunning
方法來終止線程運行
}
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow