您好,登錄后才能下訂單哦!
如何在Java中實現線程同步?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
Semaphore算是比較高級點的線程同步工具了,在許多其他語言里也有類似的實現。Semaphore有一個最大的好處就是在初始化時,可以顯式的控制并發數。其內部維護這一個c計數器,當計數器小于等于0時,是不允許其他線程訪問并發區域的,反之則可以,因此,若將并發數設置為1,則可以確保單一線程同步。下面的例子模擬多線程打印,每個線程提交打印申請,然后執行打印,最后宣布打印結束,代碼如下:
import java.util.concurrent.Semaphore; public class Program{ public static void main(String[] agrs){ PrintQueue p=new PrintQueue(); Thread[] ths=new Thread[10]; for(int i=0;i<10;i++){ ths[i]=new Thread(new Job(p),"Thread"+i); } for(int i=0;i<10;i++){ ths[i].start(); } } } class PrintQueue{ private Semaphore s; public PrintQueue(){ s=new Semaphore(1);//二進制信號量 } public void printJob(Object document){ try{ s.acquire(); long duration=(long)(Math.random()*100); System.out.printf("線程名:%s 睡眠:%d",Thread.currentThread().getName(),duration); Thread.sleep(duration); } catch(InterruptedException e){ e.printStackTrace(); } finally{ s.release(); } } } class Job implements Runnable{ private PrintQueue p; public Job(PrintQueue p){ this.p=p; } @Override public void run(){ System.out.printf("%s:正在打印一個任務\n ",Thread.currentThread().getName()); this.p.printJob(new Object()); System.out.printf("%s:文件已打印完畢\n ",Thread.currentThread().getName()); } }
執行結果如下:
Thread0:正在打印一個任務
Thread9:正在打印一個任務
Thread8:正在打印一個任務
Thread7:正在打印一個任務
Thread6:正在打印一個任務
Thread5:正在打印一個任務
Thread4:正在打印一個任務
Thread3:正在打印一個任務
Thread2:正在打印一個任務
Thread1:正在打印一個任務
線程名:Thread0 睡眠:32 Thread0:文件已打印完畢
線程名:Thread9 睡眠:44 Thread9:文件已打印完畢
線程名:Thread8 睡眠:45 Thread8:文件已打印完畢
線程名:Thread7 睡眠:65 Thread7:文件已打印完畢
線程名:Thread6 睡眠:12 Thread6:文件已打印完畢
線程名:Thread5 睡眠:72 Thread5:文件已打印完畢
線程名:Thread4 睡眠:98 Thread4:文件已打印完畢
線程名:Thread3 睡眠:58 Thread3:文件已打印完畢
線程名:Thread2 睡眠:24 Thread2:文件已打印完畢
線程名:Thread1 睡眠:93 Thread1:文件已打印完畢
可以看到,所有線程提交打印申請后,按照并發順序一次執行,沒有任何并發沖突,誰先獲得信號量,誰就先執行,其他剩余線程均等待。這里面還有一個公平信號與非公平信號之說:基本上java所有的多線程工具都支持初始化的時候指定一個布爾變量,true時表明公平,即所有處于等待的線程被篩選的條件為“誰等的時間長就選誰進行執行”,有點first in first out的感覺,而false時則表明不公平(默認是不non-fairness),即所有處于等待的線程被篩選執行是隨機的。這也就是為什么多線程往往執行順序比較混亂的原因。
若將上面的代碼改為s=new Semaphore(3);//即讓其每次可以并發3條線程
,則輸出如下:
Thread0:正在打印一個任務
Thread9:正在打印一個任務
Thread8:正在打印一個任務
Thread7:正在打印一個任務
Thread6:正在打印一個任務
Thread5:正在打印一個任務
Thread3:正在打印一個任務
Thread4:正在打印一個任務
Thread2:正在打印一個任務
Thread1:正在打印一個任務
線程名:Thread9 睡眠:26線程名:Thread8 睡眠:46線程名:Thread0 睡眠:79 Thread9:文件已打印完畢
線程名:Thread7 睡眠:35 Thread8:文件已打印完畢
線程名:Thread6 睡眠:90 Thread7:文件已打印完畢
線程名:Thread5 睡眠:40 Thread0:文件已打印完畢
線程名:Thread3 睡眠:84 Thread5:文件已打印完畢
線程名:Thread4 睡眠:13 Thread4:文件已打印完畢
線程名:Thread2 睡眠:77 Thread6:文件已打印完畢
線程名:Thread1 睡眠:12 Thread1:文件已打印完畢
Thread3:文件已打印完畢
Thread2:文件已打印完畢
很明顯已經并發沖突了。若要實現分組(每組3個)并發嗎,則每一組也要進行同步,代碼修改如下:
import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Program{ public static void main(String[] agrs){ PrintQueue p=new PrintQueue(); Thread[] ths=new Thread[10]; for(int i=0;i<10;i++){ ths[i]=new Thread(new Job(p),"Thread"+i); } for(int i=0;i<10;i++){ ths[i].start(); } } } class PrintQueue{ private Semaphore s; private boolean[] freePrinters; private Lock lock; public PrintQueue(){ s=new Semaphore(3);//二進制信號量 freePrinters=new boolean[3]; for(int i=0;i<3;i++){ freePrinters[i]=true; } lock=new ReentrantLock(); } public void printJob(Object document){ try{ s.acquire(); int printerIndex=getIndex(); long duration=(long)(Math.random()*100); System.out.printf("線程名:%s 睡眠:%d\n",Thread.currentThread().getName(),duration); Thread.sleep(duration); freePrinters[printerIndex]=true;//恢復信號,供下次使用 } catch(InterruptedException e){ e.printStackTrace(); } finally{ s.release(); } } //返回一個內部分組后的同步索引 public int getIndex(){ int index=-1; try{ lock.lock(); for(int i=0;i<freePrinters.length;i++){ if(freePrinters[i]){ freePrinters[i]=false; index=i; break; } } } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } return index; } } class Job implements Runnable{ private PrintQueue p; public Job(PrintQueue p){ this.p=p; } @Override public void run(){ System.out.printf("%s:正在打印一個任務\n ",Thread.currentThread().getName()); this.p.printJob(new Object()); System.out.printf(" %s:文件已打印完畢\n ",Thread.currentThread().getName()); } }
其中getIndex()
方法主要為了維護內部分組后(支持并發3個)組內數據的同步(用lock來同步)。
輸出如下:
Thread0:正在打印一個任務
Thread9:正在打印一個任務
Thread8:正在打印一個任務
Thread7:正在打印一個任務
Thread6:正在打印一個任務
Thread5:正在打印一個任務
Thread4:正在打印一個任務
Thread3:正在打印一個任務
Thread2:正在打印一個任務
Thread1:正在打印一個任務
線程名:Thread0 睡眠:82 打印機:0號
線程名:Thread8 睡眠:61 打印機:2號
線程名:Thread9 睡眠:19 打印機:1號
Thread9:文件已打印完畢
線程名:Thread7 睡眠:82 打印機:1號
Thread8:文件已打印完畢
線程名:Thread6 睡眠:26 打印機:2號
Thread0:文件已打印完畢
線程名:Thread5 睡眠:31 打印機:0號
Thread6:文件已打印完畢
線程名:Thread4 睡眠:44 打印機:2號
Thread7:文件已打印完畢
線程名:Thread3 睡眠:54 打印機:1號
Thread5:文件已打印完畢
線程名:Thread2 睡眠:48 打印機:0號
Thread4:文件已打印完畢
線程名:Thread1 睡眠:34 打印機:2號
Thread3:文件已打印完畢
Thread2:文件已打印完畢
Thread1:文件已打印完畢
CountDownLatch同樣也是支持多任務并發的一個工具。它主要用于“等待多個并發事件”,它內部也有一個計數器,當調用await()
方法時,線程處于等待狀態,只有當內部計數器為0時才繼續(countDown()
方法來減少計數),也就說,假若有一個需求是這樣的:主線程等待所有子線程都到達某一條件時才執行,那么只需要主線程await,然后在啟動每個子線程的時候進行countDown操作。下面模擬了一個開會的例子,只有當所有人員都到齊了,會議才能開始。
import java.util.concurrent.CountDownLatch; public class Program{ public static void main(String[] agrs){ //開啟可容納10人的會議室 VideoConference v=new VideoConference(10); new Thread(v).start(); //參與人員陸續進場 for(int i=0;i<10;i++){ Participant p=new Participant(i+"號人員",v); new Thread(p).start(); } } } class VideoConference implements Runnable{ private CountDownLatch controller; public VideoConference(int num){ controller=new CountDownLatch(num); } public void arrive(String name){ System.out.printf("%s 已經到達!\n",name); controller.countDown(); System.out.printf("還需要等 %d 個成員!\n",controller.getCount()); } @Override public void run(){ try{ System.out.printf("會議正在初始化...!\n"); controller.await(); System.out.printf("所有人都到齊了,開會吧!\n"); } catch(InterruptedException e){ e.printStackTrace(); } } } class Participant implements Runnable{ private VideoConference conference; private String name; public Participant(String name,VideoConference conference){ this.name=name; this.conference=conference; } @Override public void run(){ long duration=(long)(Math.random()*100); try{ Thread.sleep(duration); conference.arrive(this.name); } catch(InterruptedException e){ } } }
輸出:
會議正在初始化...!
0號人員 已經到達!
還需要等 9 個成員!
1號人員 已經到達!
還需要等 8 個成員!
9號人員 已經到達!
還需要等 7 個成員!
4號人員 已經到達!
還需要等 6 個成員!
8號人員 已經到達!
還需要等 5 個成員!
5號人員 已經到達!
還需要等 4 個成員!
6號人員 已經到達!
還需要等 3 個成員!
3號人員 已經到達!
還需要等 2 個成員!
7號人員 已經到達!
還需要等 1 個成員!
2號人員 已經到達!
還需要等 0 個成員!
所有人都到齊了,開會吧!
import java.util.concurrent.Phaser; import java.util.concurrent.TimeUnit; import java.util.List; import java.util.ArrayList; import java.io.File; import java.util.Date; public class Program{ public static void main(String[] agrs){ Phaser phaser=new Phaser(3); FileSearch system=new FileSearch("C:\\Windows", "log",phaser); FileSearch apps=new FileSearch("C:\\Program Files","log",phaser); FileSearch documents=new FileSearch("C:\\Documents And Settings","log",phaser); Thread systemThread=new Thread(system,"System"); systemThread.start(); Thread appsThread=new Thread(apps,"Apps"); appsThread.start(); Thread documentsThread=new Thread(documents, "Documents"); documentsThread.start(); try { systemThread.join(); appsThread.join(); documentsThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Terminated: "+ phaser.isTerminated()); } } class FileSearch implements Runnable{ private String initPath; private String end; private List<String> results; private Phaser phaser; public FileSearch(String initPath,String end,Phaser phaser){ this.initPath=initPath; this.end=end; this.results=new ArrayList<String>(); this.phaser=phaser; } private void directoryProcess(File file){ File[] files=file.listFiles(); if(files!=null){ for(int i=0;i<files.length;i++){ if(files[i].isDirectory()){ directoryProcess(files[i]); } else{ fileProcess(files[i]); } } } } private void fileProcess(File file){ if(file.getName().endsWith(end)){ results.add(file.getAbsolutePath()); } } private void filterResults(){ List<String> newResults=new ArrayList<String>(); long actualDate=new Date().getTime(); for(int i=0;i<results.size();i++){ File file=new File(results.get(i)); long fileDate=file.lastModified(); if(actualDate-fileDate<TimeUnit.MILLISECONDS.convert(1,TimeUnit.DAYS)){ newResults.add(results.get(i)); } } results=newResults; } private boolean checkResults(){ if(results.isEmpty()){ System.out.printf("%s: Phase %d: 0 results.\n",Thread.currentThread().getName(),phaser.getPhase()); System.out.printf("%s: Phase %d: End.\n",Thread.currentThread().getName(),phaser.getPhase()); phaser.arriveAndDeregister(); } else{ System.out.printf("%s: Phase %d: %d results.\n",Thread.currentThread().getName(),phaser.getPhase(),results.size()); phaser.arriveAndAwaitAdvance(); return true; } } private void showInfo() { for (int i=0; i<results.size(); i++){ File file=new File(results.get(i)); System.out.printf("%s: %s\n",Thread.currentThread().getName(),file.getAbsolutePath()); } phaser.arriveAndAwaitAdvance(); } @Override public void run(){ File file=new File(initPath); if(file.isDirectory()){ directoryProcess(file); } if(!checkResults()){ return; } filterResults(); if(!checkResults()){ return; } showInfo(); phaser.arriveAndDeregister(); System.out.printf("%s: Work completed.\n",Thread.currentThread().getName()); } }
運行結果:
Apps: Phase 0: 4 results.
System: Phase 0: 27 results.
Java的特點有哪些 1.Java語言作為靜態面向對象編程語言的代表,實現了面向對象理論,允許程序員以優雅的思維方式進行復雜的編程。 2.Java具有簡單性、面向對象、分布式、安全性、平臺獨立與可移植性、動態性等特點。 3.使用Java可以編寫桌面應用程序、Web應用程序、分布式系統和嵌入式系統應用程序等。
看完上述內容,你們掌握如何在Java中實現線程同步的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。