关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)
在多線程應用程序中經常會遇到線程同步的問題。比如:兩個線程A、線程B可能會 “同時” 執行同一段代碼,或修改同一個變量。而很多時候我們是不希望這樣的。
這時候,就需要用到線程同步。
多線程引發的問題
為了演示多線程引發問題,我們模仿買票,寫一個簡單的小程序。
實現Runnable模擬買票public class SellTicket implements Runnable {
//有30張票
private int tickets=30;
public void run() {
//寫一個死循環,模擬在不斷的賣票。
while (true){
//票數大于零,代表還有票,繼續賣。
//如果票數小于等于零,也就是沒票了。跳出循環,停止賣票
if(tickets > 0){
sell();
}else{
break;
}
}
}
//買票方法,模擬買票的動作
public void sell(){
//記錄下現在的票數
int oldNumber = tickets;
//賣掉一張后的票數,--tickets代表票數減一,模擬賣掉了一張票
int nowNumber = --tickets;
System.out.println(Thread.currentThread().getName()+",賣出了第("+ oldNumber +")張票,還剩("+ nowNumber +")張票。");
}
}
是線程模擬窗口買票public class MyTest {
public static void main(String[] args) {
//使用同一個免票實例對象
//所以,由于下面所有的窗口都用的是這一個對象。所以他們的票也都是sellTicket的tickets屬性。
SellTicket sellTicket=new SellTicket();
//模擬多個窗口同時買票,每個線程代表一個買票窗口
//Tread()的第二個參數,代表線程名。用線程名,模擬窗口名。
Thread window1=new Thread(sellTicket,"窗口-1");
Thread window2=new Thread(sellTicket,"窗口-2");
Thread window3=new Thread(sellTicket,"窗口-3");
Thread window4=new Thread(sellTicket,"窗口-4");
Thread window5=new Thread(sellTicket,"窗口-5");
//各個窗口開始工作
window1.start();
window2.start();
window3.start();
window4.start();
window5.start();
}
}
> 輸出:
窗口-3,賣出了第(28)張票,還剩(27)張票。
窗口-5,賣出了第(30)張票,還剩(29)張票。
窗口-1,賣出了第(26)張票,還剩(25)張票。
窗口-2,賣出了第(27)張票,還剩(26)張票。
...
...
窗口-2,賣出了第(6)張票,還剩(5)張票。
窗口-5,賣出了第(4)張票,還剩(3)張票。
窗口-3,賣出了第(2)張票,還剩(1)張票。
窗口-4,賣出了第(0)張票,還剩(-1)張票。
窗口-2,賣出了第(1)張票,還剩(0)張票。
上面的例子模擬了多窗口買票,但是看,輸出結果是不是又問題?怎么還有第(-1)張票。難道還有站票不成?當然不存在的,這是我們程序出現了問題。
這就是多線程同時操作統一參數的問題。也就是上面例子中SellTicket對象的private int tickets=30;屬性。
p.s.我運行了好多遍都沒出現錯誤的情況,后面給每個方法休眠了0.5秒才出現上面的錯誤結果。說明多線程不同步代碼發生的錯誤不是百分之百的,只是有一定的概率。
為什么會出現上面這種情況?
眾所周知,多線程的同步不是真的同步執行的。只是CPU切換運行線程所以看上去是幾個線程同步執行。理解了這個概念往下看。(實在不懂的可以百度一下其他大佬的文章,我之后可能還會寫一個筆記來記錄-如果有必要的話)
我們一下最后三個輸出結果,我們發現出錯的是窗口4,也就是第四個線程。...以上的可以省略,當然上面的也可能出現問題。比如兩個不同的窗口賣出了同一張票。但是我沒運行出來這個結果。。。。
窗口-3,賣出了第(2)張票,還剩(1)張票。
窗口-4,賣出了第(0)張票,還剩(-1)張票。
窗口-2,賣出了第(1)張票,還剩(0)張票。
出錯的代碼在下面。//在這里檢測當前票數
if(tickets > 0){
sell();
}else{
break;
}
代碼運行步驟如下:
到最后還剩一張票的時候。這時候 “窗口-4的線程” 運行了。它查看了一下當前票數,發現還有一張。然后進入到if()方法準備運行下面的代碼。
sell();
但是,“窗口-4的線程” 還沒來得及運行sell()方法把票減一,或者運行到sell()里面,還沒來得及減去僅剩的一張票,這時候CPU把運行的權力送給了 “窗口-2的線程” 。此時票數還是1。
“窗口-2的線程” 動作比較快,它很快的檢測當前票數是“1”,并進入if()方法,然后運行sell();將票數減一。
這時候再到 “窗口-4的線程” 此時票數就只剩下0了,但是他還是得運行sell()。也就只能賣出了第(0)張票,還剩(-1)張票了。
Java線程同步
為了避免上面的情況,可以使用以下三個方法來解決。
同步代碼塊
同步方法
同步鎖
同步代碼塊
直接上代碼
synchronized (this){
//這里的代碼,只允許一個線程運行。
//等一個線程運行結束,把鎖交給下一個等待的線程運行。
}
說明
1.this: 這里的this是同步鎖也叫同步監聽對象。
2.this可以是任何對象。
3.但是一般把當前多線程,并發訪問的共同資源當作同步鎖。在例子中也就是sellTicket對象。所以在對象里面可以寫程this。
修改上面的例子
public void run() {
//寫一個死循環,模擬在不斷的賣票。
while (true){
//票數大于零,代表還有票,繼續賣。
//如果票數小于等于零,也就是沒票了。跳出循環,停止賣票
synchronized (this.getClass()){
if(tickets > 0){
sell();
}else{
break;
}
}
}
}
同步方法
同步方法,其實跟同步代碼塊差不多,不過這個是在方法上添加synchronized關鍵字。
public synchronized void sell(){
//這里的代碼,只允許一個線程運行。
//等一個線程運行結束,把鎖交給下一個等待的線程運行。
}
要在上面的例子中使用同步方法,需要改一下。把if()判斷放到sell()方法里面。
public synchronized void sell(){
//在sell()方法在判斷下當前票數
if(tickets > 0){
//記錄下現在的票數
int oldNumber = tickets;
//賣掉一張后的票數,--tickets代表票數減一,模擬賣掉了一張票
int nowNumber = --tickets;
System.out.println(Thread.currentThread().getName()+",賣出了第("+ oldNumber +")張票,還剩("+ nowNumber +")張票。");
}
}
同步鎖
以上兩種方法,都需要一個關鍵字synchronized。同步鎖需要用到一個接口。
public interface Lock {
//生源n多代碼,詳細的去看源碼。
}
接口里面有兩個比較重要的方法。
/**
* 請求一個鎖
* Acquires the lock.
*/
void lock();
/**
* 釋放鎖
* Releases the lock.
*/
void unlock();
在這兩個方法之間的代碼都是同步的。
但是Lock只是個接口,沒法使用。
JUC包給我們一個常用的實現類ReentrantLock。部分源碼如下:
public class ReentrantLock implements Lock, java.io.Serializable {
//感興趣的可以去看看源碼
}
修改我們的案例代碼
public class SellTicket implements Runnable {
//有30張票
private int tickets=30;
Lock lock=new ReentrantLock();
public void run() {
//寫一個死循環,模擬在不斷的賣票。
while (true){
//票數大于零,代表還有票,繼續賣。
//如果票數小于等于零,也就是沒票了。跳出循環,停止賣票
lock.lock();
//synchronized (this.getClass()){
if(tickets > 0){
sell();
}else{
break;
}
//}
lock.unlock();
}
}
//買票方法,模擬買票的動作
public synchronized void sell(){
//記錄下現在的票數
int oldNumber = tickets;
//賣掉一張后的票數,--tickets代表票數減一,模擬賣掉了一張票
int nowNumber = --tickets;
System.out.println(Thread.currentThread().getName()+",賣出了第("+ oldNumber +")張票,還剩("+ nowNumber +")張票。");
}
}
瞎總結
需要同步就要加上鎖。代碼鎖了,其他線程就只能在門外等著。
無論是同步塊、同步方法、鎖機制。都有一個范圍。前兩個是在大括號中間{},后者在兩個方法中間lock();和unlock();
但是加上同步的時候,里面的代碼就只能由一個線程一次執行完。這樣就違反我們使用多線程的目的。(俗稱效率低下)
所以為了提高效率,我們要盡量想辦法,把加鎖的代碼范圍縮小,縮小,再縮小。(前提是程序不會因為多線程出問題,畢竟安全比效率重要)
標簽:tickets,同步,JAVA,張票,筆記,線程,窗口,票數
來源: https://www.cnblogs.com/Eastry/p/13081157.html
總結
以上是生活随笔為你收集整理的关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java用thinkpadx1_还用老的
- 下一篇: app上传头像处理Java_java后台