[英]How does a synchronized method work in Java?
我需要一個方法,無論調用多少線程,它只能在后台運行一次。 我使用此代碼找到了部分解決方案:
public static void async(final int a){
Thread th = new Thread(new Runnable() {
@Override
public void run() {
meth(a);
}
});
th.start();
}
public static synchronized void meth(final int a){
try {
Thread.sleep(1000);
System.out.println(a);
} catch (InterruptedException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
}
但是當我這樣測試時:
System.out.println("start");
async(11);
async(12);
async(13);
async(14);
async(15);
async(16);
async(17);
async(18);
async(19);
System.out.println("end");
我得到了那些結果:
開始結束11 19 18 17 15 16 14 13 12
我的代碼有什么問題嗎? 為什么結果與通話順序不同?
使用Thread.join后編輯
public static Object obg = new Object();
public static void async(final int a){
Thread th = new Thread(new Runnable() {
@Override
public void run() {
meth(a);
}
});
th.start();
try {
th.join();
} catch (InterruptedException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static synchronized void meth(final int a){
try {
Thread.sleep(1000);
System.out.println(a);
} catch (InterruptedException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
}
我得到了可以取消后台工作的結果:
開始11 12 13 14 15 16 17 18 19結束
Thread.join沒有給我我想要的結果
第三次編輯以提供其他語言的示例。
我在C#中嘗試了相同的代碼
static void Main(string[] args)
{
Console.WriteLine("start");
async(11);
async(12);
async(13);
async(14);
async(15);
async(16);
async(17);
async(18);
Console.WriteLine("end");
}
static Object o = new Object();
public static void async(int a){
new Thread(() =>
{
lock (o)
{
Thread.Sleep(1000);
Console.WriteLine(a);
}
}).Start();
}
結果是相同的順序
快速語言的測試結果與c#相同
所以我的問題是:如何在Java中實現這些結果
編輯:在聯接中使用新創建的線程的結果
代碼:
public static void main(String[] args) throws Exception {
System.out.println("start");
async(19,async(18,async(17,async(16,async(15,async(14,async(13,async(12,async(11,null)))))))));
System.out.println("end");
}
public static Object obg = new Object();
public static Thread async(final int a,final Thread other){
Thread th = new Thread(new Runnable() {
@Override
public void run() {
meth(a);
}
});
th.start();
try {
if(other!=null){
other.join();
}
} catch (InterruptedException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
return th;
}
public static synchronized void meth(final int a){
try {
Thread.sleep(1000);
System.out.println(a);
} catch (InterruptedException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
}
結果:
開始11 12 13 14 15 16 17 18結束19
后台工作也被取消。
您的主線程將啟動其他9個線程,給每個子線程一個不同的a
,所有這9個子線程所做的第一件事就是睡眠一秒鍾。
Thread.sleep()
調用中的時間分辨率是不確定的-取決於底層的操作系統-並且很可能所有線程在系統時鍾的同一滴答中都無法喚醒。
Java不保證線程將按照其Thread
對象被start()
的順序開始運行,也不保證它們將按進入睡眠的順序被喚醒。
任何時候,只要您希望事情以某種順序發生, 最好的方法就是在一個線程中完成所有事情。
我需要此部分按順序運行...我不希望它同時運行兩次或多次...我仍然希望它在后台運行。
好吧,我明白了。
您可能要使用java.util.concurrent.Executors.newSingleThreadExecutor()
。 該函數將返回帶有單個工作線程的線程池 。
輔助線程將“在后台”運行您提交到池中的任務,並且它將按照提交順序一次運行一次。
static final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
public static void async(final int a) {
singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
meth(a);
}
});
}
您似乎在這里有兩個不同的問題。 首先要說的是,“需要一個在后台運行的方法,無論調用多少線程,它都只能運行一次”,這是一個問題,然后繼續說,不同線程的結果不會發生按照您想要的順序。
問題1:使方法僅運行一次
您沒有指定“無論運行多少線程,僅運行一次”的確切含義。 對於多線程編程,可能有一些不同的常見含義。由於您提供了示例代碼,因此我假設您的意思是一次只想要一個,但是想要多次調用該方法。
對於這個問題,您的方向正確。 如果您想一次只允許一個線程訪問某些數據或操作,則正確的方法是使用鎖(也稱為互斥鎖)。 在Java中,這是通過創建一個對象來充當鎖來完成的。 該對象可以是任何類類型-該部分並不重要。 重要的是,所有需要互斥訪問某些數據或操作的線程都使用鎖定對象的相同實例。 在訪問數據或操作之前,必須先在對象上獲得一個鎖,然后再釋放該鎖。
現在,將理論部分排除在外,可以更輕松地說明已應用於方法的“同步”關鍵字。 當您synchronized
應用到Java方法時,會發生的事是“擁有”該方法的對象“ this”(在您的情況下,無論哪個對象實例擁有對meth(int)的調用),該對象都用作一把鎖。 上獲得的鎖this
被執行的方法的前體,並且該方法的主體完成執行之后鎖定被解除。 這僅允許一個線程一次訪問該代碼。
但是請記住,不同的線程需要具有相同的鎖對象實例。 如果您擁有多個具有任何類型的對象,則該對象的不同實例可以使它們的同步方法彼此並發運行,因為它們具有單獨的鎖 。
考慮到您已經在使用synchronized
,這足以回答這個問題的這一部分。 要進一步閱讀,請參閱我將在底部提供的鏈接。
問題2:為什么這些線程發生故障?
這個問題有一個快速簡便的答案:如果您沒有專門做某些事情導致其不然,那么就無法保證線程將以任何特定順序發生。 它是如此簡單。
在您的情況下,每個線程的第一件事就是睡一秒鍾,但這是該線程操作的一部分。 基本上,所有線程基本上都在同一時間休眠一秒鍾,然后所有線程在同一時間休眠。 並且不能保證他們將執行什么順序。
如果您使它在新線程的實例化之間處於休眠狀態,則可能會使執行按預期的順序進行。 也就是說, async(1); sleep; async(2); sleep; async(3); etc..
async(1); sleep; async(2); sleep; async(3); etc..
async(1); sleep; async(2); sleep; async(3); etc..
可能會給您帶來更像您期望的結果,盡管這會在每次async
調用之間async
睡眠,因此需要很長時間才能完成所有工作。 但是, 請注意,這不是您應完成的工作的方式。 盡管如果您在async
調用之間睡眠時,您可能會按照想要的順序獲得輸出,但即使這樣,也不能保證獲得該順序。 例如,如果您的系統因其他活動而陷入困境,那么它可能會拖延時間,以至於對meth
一次調用要花費兩秒鍾以上的時間,因此您有多個線程在等待鎖,然后再次從中獲取結果。訂購。
沒有某種類型的直接干預,就永遠不能保證線程執行順序。 如果T1和T2都被執行,或者都在等待聯接,或者出於任何原因想要運行,那么您永遠都不知道哪個會先發生。 通常不需要知道。
如果您需要強制線程以一定順序發生-為了最大程度地發揮線程的生命力而通常想要避免的事情,但是有時這是必要的-那么請嘗試看一下其中的一些特殊情況Java並發對象可以幫助您獲得所需的時間。 您可以在下面的鏈接中找到其中一些。
在您要提供給我們的非常特殊的情況下,如果您只想使用線性順序(甚至是樹狀順序),其中有一組線程{T1,T2,...,Tn},則可以使用join
,就像您開始要做的那樣(您再次關閉!),但是讓每個線程先加入一個線程,而不是全部加入一個線程。 也就是說,讓T2加入T1,T3加入T2,依此類推。 您可以通過這種方式執行線性執行,還可以得到樹狀模式:如果您只關心T3和T4在T2之后發生,但您不在乎哪個首先在T3和T4之間發生,那么它們都可以加入T2-在可以增加活力的地方這樣做。
如果最終目標完全像此處的問題一樣,它們都是線性執行的,那么最好在一個線程中完成,但要有一個線程需要執行的一系列操作,那么一個線程就可以執行這些事情按順序,每次執行所有操作,然后一無所有,則等待更多操作。 本段不會回答您的問題,但這是關於另一種方法的建議,根據您的使用情況,可能更好也可能更好。
還有我提到的鏈接
Java並發跟蹤 (編輯:對不起,我是第一次把鏈接搞砸了。已修復。)
徹底檢查該資源。 請注意那里的目錄,以幫助您瀏覽Java並發性子主題。 開始時可能很難理解某些示例,但是您似乎在朝着正確的方向前進。 繼續吧
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.