并发线程指南之线程同步工具

在第二章基本的线程同步中,我们学习了同步和critical section的内容。基本上,当多个并发任务共享一个资源时就称为同步,例如:一个对象或者一个对象的属性。访问这个资源的代码块称为:临界区。
 
如果机制没有使用恰当,那么可能会导致错误的结果,或者数据不一致,又或者出现异常情况。所以必须采取java语言提供的某个恰当的同步机制来避免这些问题。
 
在第二章,基本的线程同步中,我们学会了以下2个同步机制:
 
关键词同步
Lock接口和它的实现类们:ReentrantLock, ReentrantReadWriteLock.ReadLock, 和 ReentrantReadWriteLock.WriteLock
在此章节,我们将学习怎样使用高等级的机制来达到多线程的同步。这些高等级机制有:
Semaphores: 控制访问多个共享资源的计数器。此机制是并发编程的最基本的工具之一,而且大部分编程语言都会提供此机制。
CountDownLatch: CountDownLatch 类是Java语言提供的一个机制,它允许线程等待多个操作的完结。
CyclicBarrier: CyclicBarrier 类是又一个java语言提供的机制,它允许多个线程在同一个点同步。
Phaser: Phaser类是又一个java语言提供的机制,它控制并发任务分成段落来执行。全部的线程在继续执行下一个段之前必须等到之前的段执行结束。这是Java 7 API的一个新特性。
Exchanger: Exchanger类也是java语言提供的又一个机制,它提供2个线程间的数据交换点。
Semaphores是最基本的同步机制可以用来在任何问题中保护任何critical section。其他的机制只有在之前描述的那些有特殊特点的应用中使用。请根据你的应用的特点来选择适当的机制。
控制并发访问资源::::::
这个指南,你将学习怎样使用Java语言提供的Semaphore机制。Semaphore是一个控制访问多个共享资源的计数器。
Semaphore的内容是由Edsger Dijkstra引入并在 THEOS操作系统上第一次使用。
当一个线程想要访问某个共享资源,首先,它必须获得semaphore。如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许访问共享的资源。计数器的值大于0表示,有可以自由使用的资源,所以线程可以访问并使用它们。
另一种情况,如果semaphore的计数器的值等于0,那么semaphore让线程进入休眠状态一直到计数器大于0。计数器的值等于0表示全部的共享资源都正被线程们使用,所以此线程想要访问就必须等到某个资源成为自由的。
当线程使用完共享资源时,他必须放出semaphore为了让其他线程可以访问共享资源。这个操作会增加semaphore的内部计数器的值。

在这个指南里,你将学习如何使用Semaphore类来实现一种比较特殊的semaphores种类,称为binary semaphores。这个semaphores种类保护访问共享资源的独特性,所以semaphore的内部计数器的值只能是1或者0。为了展示如何使用它,你将要实现一个PrintQueue类来让并发任务打印它们的任务。这个PrintQueue类会受到binary semaphore的保护,所以每次只能有一个线程可以打印。

package demo14;
import java.util.concurrent.Semaphore;
public class PrintQueue {
private final Semaphore semaphore;
public PrintQueue() {
semaphore = new Semaphore(1);//初始值为1
}
public void printJob(Object document){
try {
semaphore.acquire();
long duration = (long) (Math.random()*10);
Thread.sleep(duration);
} catch (InterruptedException e) {
// TODO: handle exception
}finally{
semaphore.release();
}
}
}
这个例子的关键是PrintQueue类的printJob()方法。此方法展示了3个你必须遵守的步骤当你使用semaphore来实现critical section时,并保护共享资源的访问:
1. 首先, 你要调用acquire()方法获得semaphore。
2. 然后, 对共享资源做出必要的操作。
3. 最后, 调用release()方法来释放semaphore。
另一个重点是PrintQueue类的构造方法和初始化Semaphore对象。你传递值1作为此构造方法的参数,那么你就创建了一个binary semaphore。初始值为1,就保护了访问一个共享资源,在例子中是print queue。
当你开始10个threads,当你开始10个threads时,那么第一个获得semaphore的得到critical section的访问权。剩下的线程都会被semaphore阻塞直到那个获得semaphore的线程释放它。当这情况发生,semaphore在等待的线程中选择一个并给予它访问critical section的访问权。全部的任务都会打印文档,只是一个接一个的执行。
Semaphore类有另2个版本的 acquire() 方法:
 
acquireUninterruptibly():acquire()方法是当semaphore的内部计数器的值为0时,阻塞线程直到semaphore被释放。在阻塞期间,线程可能会被中断,然后此方法抛出InterruptedException异常。而此版本的acquire方法会忽略线程的中断而且不会抛出任何异常。
tryAcquire():此方法会尝试获取semaphore。如果成功,返回true。如果不成功,返回false值,并不会被阻塞和等待semaphore的释放。接下来是你的任务用返回的值执行正确的行动。
Semaphores的公平性
 
fairness的内容是指全java语言的所有类中,那些可以阻塞多个线程并等待同步资源释放的类(例如,semaphore)。默认情况下是非公平模式。在这个模式中,当同步资源释放,就会从等待的线程中任意选择一个获得资源,但是这种选择没有任何标准。而公平模式可以改变这个行为并强制选择等待最久时间的线程。
 
随着其他类的出现,Semaphore类的构造函数容许第二个参数。这个参数必需是 Boolean 值。如果你给的是 false 值,那么创建的semaphore就会在非公平模式下运行。如果你不使用这个参数,是跟给false值一样的结果。如果你给的是true值,那么你创建的semaphore就会在公平模式下运行。
控制并发访问多个资源:::::::::::::::::
并发访问资源的控制中,你学习了信号量(semaphores)的基本知识。
在上个指南,你实现了使用binary semaphores的例子。那种semaphores是用来保护访问一个共享资源的,或者说一个代码片段每次只能被一个线程执行。但是semaphores也可以用来保护多个资源的副本,也就是说当你有一个代码片段每次可以被多个线程执行。
在这个指南中,你将学习怎样使用semaphore来保护多个资源副本。你将实现的例子会有一个print queue但可以在3个不同的打印机上打印文件。
1. package demo14;
2. import java.util.concurrent.Semaphore;
3. import java.util.concurrent.locks.Lock;
4. import java.util.concurrent.locks.ReentrantLock;
5. public class PrintQueue {
6. private final Semaphore semaphore;
7. private boolean freePrinters[];
8. private Lock lock;
9. public PrintQueue() {
10. semaphore = new Semaphore(3);//
11. freePrinters = new boolean[3];
12. for (int i = 0; i < 3; i++) {
13. freePrinters[i] = true;
14. }
15. lock = new ReentrantLock();
16. }
17. public void printJob(Object document){
18. try {
19. semaphore.acquire();//只有可能在这里发生异常。所以改变数组的值必须写在这里面。
20. //获得打印机号码
21. int assignedPrinter = getPrinter();
22. long duration = (long) (Math.random()*10);
23. Thread.sleep(duration);
24. freePrinters[assignedPrinter]=true;
25. } catch (InterruptedException e) {
26. // TODO: handle exception
27. }finally{
28. semaphore.release();
29. }
30. }
31. private int getPrinter() {
32. int ret = -1;
33. try {
34. lock.lock();
35. for (int i = 0; i < freePrinters.length; i++) {
36. if (freePrinters[i]) {
37. ret = i;
38. freePrinters[i] = false;
39. break;
40. }
41. }
42. } catch (Exception e) {
43. // TODO: handle exception
44. }finally{
45. lock.unlock();
46. }
47. return ret;
48. }
49. }
在例子中的PrintQueue类的关键是:Semaphore对象创建的构造方法是使用3作为参数的。这个例子中,前3个调用acquire() 方法的线程会获得临界区的访问权,其余的都会被阻塞 。当一个线程结束临界区的访问并解放semaphore时,另外的线程才可能获得访问权。
在这个临界区,线程获得被分配打印的打印机的引索值。例子的这部分让例子更真实,而且它没有使用任何与semaphores相关的代码。
The acquire(), acquireUninterruptibly(), tryAcquire(),和release()方法有一个外加的包含一个int参数的版本。这个参数表示 线程想要获取或者释放semaphore的许可数。也可以这样说,这个线程想要删除或者添加到semaphore的内部计数器的单位数量。在这个例子中acquire(), acquireUninterruptibly(), 和tryAcquire() 方法, 如果计数器的值小于许可值,那么线程就会被阻塞直到计数器到达或者大于许可值。

等待多个并发事件完成:
Java并发API提供这样的类,它允许1个或者多个线程一直等待,直到一组操作执行完成。 这个类就是CountDownLatch类。它初始一个整数值,此值是线程将要等待的操作数。当某个线程为了想要执行这些操作而等待时, 它要使用 await()方法。此方法让线程进入休眠直到操作完成。 当某个操作结束,它使用countDown() 方法来减少CountDownLatch类的内部计数器。当计数器到达0时,这个类会唤醒全部使用await() 方法休眠的线程们。
1. package demo14;
2. import java.util.concurrent.CountDownLatch;
3. public class Videoconference implements Runnable{
4. private final CountDownLatch controller;
5. public Videoconference(int number){
6. controller = new CountDownLatch(number);
7. }
8. public void arrive(String name){
9. System.out.println(name+" has arrived");
10. controller.countDown();//减1
11. System.out.println("waiting for "+controller.getCount()+"people to arrive");
12. }
13. @Override
14. public void run() {
15. try {
16. controller.await();//等待全部线程结束countdown值为0
17. System.out.println("all the people has arrived");
18. } catch (Exception e) {
19. // TODO: handle exception
20. }
21. }
22. }
1. package demo14;
2. import java.util.concurrent.TimeUnit;
3. public class Participant implements Runnable{
4. private Videoconference conference;
5. private String name;
6. public Participant(Videoconference conference, String name) {
7. super();
8. this.conference = conference;
9. this.name = name;
10. }
11. @Override
12. public void run() {
13. // TODO Auto-generated method stub
14. long duration = (long) (Math.random()*10);
15. try {
16. TimeUnit.SECONDS.sleep(duration);
17. } catch (Exception e) {
18. // TODO: handle exception
19. }
20. conference.arrive(name);
21. }
22. }
1. public class Main {
2. public static void main(String[] args) {
3. Videoconference videoconference = new Videoconference(10);
4. Thread conference = new Thread(videoconference);
5. conference.start();
6. for (int i = 0; i < 10; i++) {
7. Participant participant = new Participant(videoconference, "thread"+i);
8. Thread thread = new Thread(participant);
9. thread.start();
10. }
11. }
12. }
CountDownLatch类有3个基本元素:
 
初始值决定CountDownLatch类需要等待的事件的数量。
await() 方法, 被等待全部事件终结的线程调用。
countDown() 方法,事件在结束执行后调用。
当创建 CountDownLatch 对象时,对象使用构造函数的参数来初始化内部计数器。每次调用 countDown() 方法, CountDownLatch 对象内部计数器减一。当内部计数器达到0时, CountDownLatch 对象唤醒全部使用 await() 方法睡眠的线程们。
 
不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。一旦计数器的值初始后,唯一可以修改它的方法就是之前用的 countDown() 方法。当计数器到达0时, 全部调用 await() 方法会立刻返回,接下来任何countDown() 方法的调用都将不会造成任何影响。
 
此方法与其他同步方法有这些不同:
 
CountDownLatch 机制不是用来保护共享资源或者临界区。它是用来同步一个或者多个执行多个任务的线程。它只能使用一次。像之前解说的,一旦CountDownLatch的计数器到达0,任何对它的方法的调用都是无效的。如果你想再次同步,你必须创建新的对象。
在同一个点同步任务::::::::
ava 并发 API 提供了可以允许2个或多个线程在在一个确定点的同步应用。它是 CyclicBarrier 类。此类与在此章节的等待多个并发事件完成指南中的 CountDownLatch 类相似,但是它有一些特殊性让它成为更强大的类。
 
CyclicBarrier 类有一个整数初始值,此值表示将在同一点同步的线程数量。当其中一个线程到达确定点,它会调用await() 方法来等待其他线程。当线程调用这个方法,CyclicBarrier阻塞线程进入休眠直到其他线程到达。当最后一个线程调用CyclicBarrier 类的await() 方法,它唤醒所有等待的线程并继续执行它们的任务。
 
CyclicBarrier 类有个有趣的优势是,你可以传递一个外加的 Runnable 对象作为初始参数,并且当全部线程都到达同一个点时,CyclicBarrier类 会把这个对象当做线程来执行。此特点让这个类在使用 divide 和 conquer 编程技术时,可以充分发挥任务的并行性,
 
在这个指南,你将学习如何使用 CyclicBarrier 类来让一组线程在一个确定点同步。你也将使用 Runnable 对象,它将会在全部线程都到达确定点后被执行。在这个例子里,你将在数字矩阵中查找一个数字。矩阵会被分成多个子集(使用divide 和 conquer 技术),所以每个线程会在一个子集中查找那个数字。一旦全部行程运行结束,会有一个最终任务来统一他们的结果。
1. public class MatrixMock {
2. private int data[][];
3. public MatrixMock(int size,int length,int number){
4. int counter = 0;
5. data = new int[size][length];
6. Random random = new Random();
7. for (int i = 0; i < size; i++) {
8. for (int j = 0; j < length; j++) {
9. data[i][j] = random.nextInt(10);
10. if (data[i][j] == number) {
11. counter++;
12. }
13. }
14. }
15. System.out.println("there are "+counter+" which is the same as number");
16. }
17. public int[] getRow(int row){
18. if (row > 0 && row < data.length) {
19. return data[row];
20. }
21. return null;
22. }
23. }
1. public class Results {
2. private int data[];
3. public Results(int size){
4. data = new int[size];
5. }
6. public void setData(int position ,int value){
7. data[position] = value;
8. }
9. public int[] getData(){
10. return data;
11. }
12. }
1. package demo15;
2. import java.util.concurrent.CyclicBarrier;
3. public class Search implements Runnable{
4. private int firstRow;
5. private int lastRow;
6. private MatrixMock mock;
7. private Results results;
8. private int number;
9. private final CyclicBarrier cyclicBarrier;
10. public Search(int firstRow, int lastRow, MatrixMock mock, Results results, int number, CyclicBarrier cyclicBarrier) {
11. super();
12. this.firstRow = firstRow;
13. this.lastRow = lastRow;
14. this.mock = mock;
15. this.results = results;
16. this.number = number;
17. this.cyclicBarrier = cyclicBarrier;
18. }
19. @Override
20. public void run() {
21. // TODO Auto-generated method stub
22. int counter;
23. for (int i = firstRow; i < lastRow; i++) {
24. int row[] = mock.getRow(i);
25. counter = 0;
26. for (int j = 0; j < row.length; j++) {
27. if (row[j] == number) {
28. counter++;
29. }
30. }
31. results.setData(i, counter);
32. }
33. try {
34. cyclicBarrier.await();
35. } catch (Exception e) {
36. // TODO: handle exception
37. }
38. }
39. }
1. package demo15;
2. public class Grouper implements Runnable{
3. private Results results;
4. public Grouper(Results results) {
5. super();
6. this.results = results;
7. }
8. @Override
9. public void run() {
10. // TODO Auto-generated method stub
11. int finalResult = 0;
12. int data[] = results.getData();
13. for (int i : data) {
14. finalResult += i;
15. }
16. System.out.println("total result:"+finalResult);
17. }
18. }
1. public class Main {
2. public static void main(String[] args) {
3. final int rows = 10000;
4. final int numbers = 1000;
5. final int search = 5;
6. final int participants = 5;
7. final int lines_particpants = 2000;
8. MatrixMock mock = new MatrixMock(rows, numbers, search);
9. Results results = new Results(rows);
10. Grouper grouper = new Grouper(results);
11. CyclicBarrier cyclicBarrier = new CyclicBarrier(participants,grouper);
12. Search searchs[] = new Search[participants];
13. for (int i = 0; i < searchs.length; i++) {
14. searchs[i] = new Search(i*lines_particpants, i*lines_particpants+lines_particpants, mock, results, 5, cyclicBarrier);
15. Thread thread = new Thread(searchs[i]);
16. thread.start();
17. }
18. }

例子中解决的问题比较简单。我们有一个很大的随机的整数矩阵,然后你想知道这矩阵里面某个数字出现的次数。为了更好的执行,我们使用了 divide 和 conquer 技术。我们 divide 矩阵成5个子集,然后在每个子集里使用一个线程来查找数字。这些线程是 Searcher 类的对象。
 
我们使用 CyclicBarrier 对象来同步5个线程的完成,并执行 Grouper 任务处理个别结果,最后计算最终结果。
 
如我们之前提到的,CyclicBarrier 类有一个内部计数器控制到达同步点的线程数量。每次线程到达同步点,它调用 await() 方法告知 CyclicBarrier 对象到达同步点了。CyclicBarrier 把线程放入睡眠状态直到全部的线程都到达他们的同步点。
 
当全部的线程都到达他们的同步点,CyclicBarrier 对象叫醒全部正在 await() 方法中等待的线程们,然后,选择性的,为CyclicBarrier的构造函数 传递的 Runnable 对象(例子里,是 Grouper 对象)创建新的线程执行外加任务。
CyclicBarrier 类有另一个版本的 await() 方法:
await(long time, TimeUnit unit): 线程会一直休眠直到被中断;内部计数器到达0,或者特定的时间过去了。TimeUnit类有多种常量: DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, and SECONDS.
此类也提供了 getNumberWaiting() 方法,返回被 await() 方法阻塞的线程数,还有 getParties() 方法,返回将与CyclicBarrier同步的任务数。
 
重置 CyclicBarrier 对象
CyclicBarrier 类与CountDownLatch有一些共同点,但是也有一些不同。最主要的不同是,CyclicBarrier对象可以重置到它的初始状态,重新分配新的值给内部计数器,即使它已经被初始过了。
 
可以使用 CyclicBarrier的reset() 方法来进行重置操作。当这个方法被调用后,全部的正在await() 方法里等待的线程接收到一个 BrokenBarrierException 异常。此异常在例子中已经用打印stack trace处理了,但是在一个更复制的应用,它可以执行一些其他操作,例如重新开始执行或者在中断点恢复操作。
 
破坏 CyclicBarrier 对象
CyclicBarrier 对象可能处于一个特殊的状态,称为 broken。当多个线程正在 await() 方法中等待时,其中一个被中断了,此线程会收到 InterruptedException 异常,但是其他正在等待的线程将收到 BrokenBarrierException 异常,并且 CyclicBarrier 会被置于broken 状态中。
 
CyclicBarrier 类提供了isBroken() 方法,如果对象在 broken 状态,返回true,否则返回false。
运行阶段性并发任务:
Java 并发 API 提供的一个非常复杂且强大的功能是,能够使用Phaser类运行阶段性的并发任务。当某些并发任务是分成多个步骤来执行时,那么此机制是非常有用的。Phaser类提供的机制是在每个步骤的结尾同步线程,所以除非全部线程完成第一个步骤,否则线程不能开始进行第二步。  
相对于其他同步应用,我们必须初始化Phaser类与这次同步操作有关的任务数,我们可以通过增加或者减少来不断的改变这个数。
在这个指南,你将学习如果使用Phaser类来同步3个并发任务。这3个任务会在3个不同的文件夹和它们的子文件夹中搜索扩展名是.log并在24小时内修改过的文件。这个任务被分成3个步骤:
1. 在指定的文件夹和子文件夹中获得文件扩展名为.log的文件列表。
2. 过滤第一步的列表中修改超过24小时的文件。
3. 在操控台打印结果。
在步骤1和步骤2的结尾我们要检查列表是否为空。如果为空,那么线程直接结束运行并从phaser类中淘汰。
1. package demo16;
2. import java.io.File;
3. import java.util.ArrayList;
4. import java.util.Date;
5. import java.util.List;
6. import java.util.concurrent.Phaser;
7. import java.util.concurrent.TimeUnit;
8. public class FileSearch implements Runnable{
9. private String initPath;//搜索开始时候的文件夹
10. private String end;//要寻找的文件名的扩展名
11. private List<String> results;//寻找到的符合条件的路径
12. private Phaser phaser;
13. public FileSearch(String initPath, String end, Phaser phaser) {
14. this.initPath = initPath;
15. this.end = end;
16. this.phaser = phaser;
17. results = new ArrayList<>();
18. }
19. private void directoryProcess(File file){
20. File list[] = file.listFiles();
21. if (list != null) {
22. for (int i = 0; i < list.length; i++) {
23. if (list[i].isDirectory()) {
24. directoryProcess(list[i]);
25. }else {
26. fileProcess(list[i]);
27. }
28. }
29. }
30. }
31. private void fileProcess(File file){
32. if (file.getName().endsWith(end)) {
33. results.add(file.getAbsolutePath());
34. }
35. }
36. //不接收任何参数,过滤在第一阶段获得的文件列表,并删除修改超过24小时的文件
37. private void filterResults(){
38. List<String> newResults = new ArrayList<>();
39. long actualDate = new Date().getTime();
40. for (int i = 0; i < results.size(); i++) {
41. File file = new File(results.get(i));
42. long fileDate = file.lastModified();
43. if (actualDate - fileDate < TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS)) {
44. newResults.add(results.get(i));
45. }
46. results = newResults;
47. }
48. }
49. //实现checkResults,在第一个和第二个phase的结尾被调用,并检查结果是否为空。
50. private boolean checkResults(){
51. if (results.isEmpty()) {
52. phaser.arriveAndDeregister();//通知此线程已经结束并离开phased操作
53. return false;
54. }
55. phaser.arriveAndAwaitAdvance();//它会被阻塞直到phased操作的全部参与线程结束actual phaser
56. return true;
57. }
58. //打印信息
59. private void showInfo(){
60. for (int i = 0; i < results.size(); i++) {
61. File file = new File(results.get(i));
62. System.out.println(file.getAbsolutePath());
63. }
64. phaser.arriveAndAwaitAdvance();
65. }
66. @Override
67. public void run() {
68. // TODO Auto-generated method stub
69. phaser.arriveAndAwaitAdvance();//所有线程被创建完成,才会开始搜索
70. File file = new File(initPath);
71. if (file.isDirectory()) {
72. directoryProcess(file);
73. }
74. if (!checkResults()) {
75. return;
76. }
77. filterResults();
78. if (!checkResults()) {
79. return;
80. }
81. showInfo();
82. phaser.arriveAndDeregister();
83. }
84. }
1. package demo16;
2. import java.util.concurrent.Phaser;
3. public class Main {
4. public static void main(String[] args) {
5. Phaser phaser = new Phaser(3);
6. FileSearch system = new FileSearch("c:/", "log", phaser);
7. FileSearch apps = new FileSearch("d:/", "log", phaser);
8. FileSearch document = new FileSearch("e:/", "log", phaser);
9. Thread systemThread = new Thread(system, "system");
10. systemThread.start();
11. Thread appsThread = new Thread(apps, "apps");
12. appsThread.start();
13. Thread documenThread = new Thread(document, "document");
14. documenThread.start();
15. //等待三个线程的终结
16. try {
17. systemThread.join();
18. appsThread.join();
19. documenThread.join();
20. } catch (Exception e) {
21. // TODO: handle exception
22. }
23. System.out.println(phaser.isTerminated());
24. }
25. }
这程序开始创建的 Phaser 对象是用来在每个phase的末端控制线程的同步。Phaser的构造函数接收参与者的数量作为参数。在这里,Phaser有3个参与者。这个数向Phaser表示 Phaser改变phase之前执行 arriveAndAwaitAdvance() 方法的线程数,并叫醒正在休眠的线程。
 
一旦Phaser被创建,我们运行3个线程分别执行3个不同的FileSearch对象。
 
在例子里,我们使用 Windows operating system 的路径。如果你使用的是其他操作系统,那么修改成适应你的环境的路径。
 
FileSearch对象的 run() 方法中的第一个指令是调用Phaser对象的 arriveAndAwaitAdvance() 方法。像之前提到的,Phaser知道我们要同步的线程的数量。当某个线程调用此方法,Phaser减少终结actual phase的线程数,并让这个线程进入休眠 直到全部其余线程结束phase。在run() 方法前面调用此方法,没有任何 FileSearch 线程可以开始他们的工作,直到全部线程被创建。
 
在phase 1 和 phase 2 的末端,我们检查phase 是否生成有元素的结果list,或者它没有生成结果且list为空。在第一个情况,checkResults() 方法之前提的调用 arriveAndAwaitAdvance()。在第二个情况,如果list为空,那就没有必要让线程继续了,就直接返回吧。但是你必须通知phaser,将会少一个参与者。为了这个,我们使用arriveAndDeregister()。它通知phaser线程结束了actual phase, 但是它将不会继续参见后面的phases,所以请phaser不要再等待它了。
 
在phase3的结尾实现了 showInfo() 方法, 调用了 phaser 的 arriveAndAwaitAdvance() 方法。这个调用,保证了全部线程在同一时间结束。当此方法结束执行,有一个调用phaser的arriveAndDeregister() 方法。这个调用,我们撤销了对phaser线程的注册,所以当全部线程结束时,phaser 有0个参与者。
 
最后,main() 方法等待3个线程的完成并调用phaser的 isTerminated() 方法。当phaser 有0个参与者时,它进入termination状态,此状态与此调用将会打印true到操控台。
 
Phaser 对象可能是在这2中状态:
 
Active: 当 Phaser 接受新的参与者注册,它进入这个状态,并且在每个phase的末端同步。 在此状态,Phaser像在这个指南里解释的那样工作。此状态不在Java 并发 API中。
Termination: 默认状态,当Phaser里全部的参与者都取消注册,它进入这个状态,所以这时 Phaser 有0个参与者。更具体的说,当onAdvance() 方法返回真值时,Phaser 是在这个状态里。如果你覆盖那个方法,你可以改变它的默认行为。当 Phaser 在这个状态,同步方法 arriveAndAwaitAdvance()会 立刻返回,不会做任何同步。
Phaser 类的一个显著特点是你不需要控制任何与phaser相关的方法的异常。不像其他同步应用,线程们在phaser休眠不会响应任何中断也不会抛出 InterruptedException 异常。只有一个异常会在下面的‘更多’里解释。
它展示了前2个phases的执行。你可以发现Apps线程在phase 2 结束它的运行由于list 为空。当你执行例子,你会发现一些线程比其他的线程更快结束phase,但是他们必须等待其他全部结束然后才能继续。
 
更多…
 
The Phaser类还提供了其他相关方法来改变phase。他们是:
 
arrive(): 此方法示意phaser某个参与者已经结束actual phase了,但是他应该等待其他的参与者才能继续执行。小心使用此法,因为它并不能与其他线程同步。
awaitAdvance(int phase): 如果我们传递的参数值等于phaser的actual phase,此方法让当前线程进入睡眠直到phaser的全部参与者结束当前的phase。如果参数值与phaser 的 actual phase不等,那么立刻返回。
awaitAdvanceInterruptibly(int phaser): 此方法等同与之前的方法,只是在线程正在此方法中休眠而被中断时候,它会抛出InterruptedException 异常。
Phaser的参与者的注册
当你创建一个 Phaser 对象,你表明了参与者的数量。但是Phaser类还有2种方法来增加参与者的数量。他们是:
 
register(): 此方法为Phaser添加一个新的参与者。这个新加入者会被认为是还未到达 actual phase.
bulkRegister(int Parties): 此方法为Phaser添加一个特定数量的参与者。这些新加入的参与都会被认为是还未到达 actual phase.
Phaser类提供的唯一一个减少参与者数量的方法是arriveAndDeregister() 方法,它通知phaser线程已经结束了actual phase,而且他不想继续phased的操作了。
 
强制终止 Phaser
当phaser有0个参与者,它进入一个称为Termination的状态。Phaser 类提供 forceTermination() 来改变phaser的状态,让它直接进入Termination 状态,不在乎已经在phaser中注册的参与者的数量。此机制可能会很有用在一个参与者出现异常的情况下来强制结束phaser.
 
当phaser在 Termination 状态, awaitAdvance() 和 arriveAndAwaitAdvance() 方法立刻返回一个负值,而不是一般情况下的正值如果你知道你的phaser可能终止了,那么你可以用这些方法来确认他是否真的终止了。
控制并发阶段性任务的改变:::::::::
Phaser 类提供每次phaser改变阶段都会执行的方法。它是 onAdvance() 方法。它接收2个参数:当前阶段数和注册的参与者数;它返回 Boolean 值,如果phaser继续它的执行,则为 false;否则为真,即phaser结束运行并进入 termination 状态。
如果注册参与者为0,此方法的默认的实现值为真,要不然就是false。如果你扩展Phaser类并覆盖此方法,那么你可以修改它的行为。通常,当你要从一个phase到另一个,来执行一些行动时,你会对这么做感兴趣的。
在这个指南,你将学习如何控制phaser的 phase的改变,通过实现自定义版本的 Phaser类并覆盖 onAdvance() 方法来执行一些每个phase 都会改变的行动。你将要实现一个模拟测验,有些学生要完成他们的练习。全部的学生都必须完成同一个练习才能继续下一个练习。
1. package demo17;
2. import java.util.concurrent.Phaser;
3. public class MyPhaser extends Phaser{
4. @Override
5. protected boolean onAdvance(int phase, int registeredParties) {
6. switch (phase) {
7. case 0:
8. System.out.println("the exam are going to start,the students are ready");
9. return false;
10. case 1:
11. System.out.println("all the student has finished the first exam");
12. return false;
13. case 2:
14. System.out.println("all the student has finished the second exam");
15. return false;
16. case 3:
17. System.out.println("all the student has finished the third exam");
18. return true;
19. default:
20. return true;
21. }
22. }
23. }
1. package demo17;
2. import java.util.concurrent.Phaser;
3. import java.util.concurrent.TimeUnit;
4. public class Student implements Runnable{
5. private Phaser phaser;
6. public Student(Phaser phaser) {
7. super();
8. this.phaser = phaser;
9. }
10. @Override
11. public void run() {
12. // TODO Auto-generated method stub
13. phaser.arriveAndAwaitAdvance();//等待其它线程
14. doExercise();
15. phaser.arriveAndAwaitAdvance();//等待其它线程
16. doExercise();
17. phaser.arriveAndAwaitAdvance();//等待其它线程
18. doExercise();
19. phaser.arriveAndAwaitAdvance();
20. }
21. private void doExercise() {
22. // TODO Auto-generated method stub
23. try {
24. long duration = (long) (Math.random()*10);
25. TimeUnit.SECONDS.sleep(duration);
26. } catch (Exception e) {
27. // TODO: handle exception
28. }
29. }
30. }
1. public class Main {
2. public static void main(String[] args) {
3. MyPhaser phaser = new MyPhaser();
4. Student student[] = new Student[5];
5. for (int i = 0; i < student.length; i++) {
6. student[i] = new Student(phaser);
7. phaser.register();
8. }
9. Thread thread[] = new Thread[5];
10. for (int i = 0; i < thread.length; i++) {
11. thread[i] = new Thread(student[i]);
12. thread[i].start();
13. }
14. for (int i = 0; i < thread.length; i++) {
15. try {
16. thread[i].join();
17. } catch (Exception e) {
18. // TODO: handle exception
19. }
20. }
21. System.out.println(phaser.isTerminated());
22. }
23. }
这个练习模拟了有3个测验的真实测试。全部的学生必须都完成同一个测试才能开始下一个测试。为了实现这个必须使用同步,我们使用了Phaser类,但是你实现了你自己的phaser通过扩展原来的类,并覆盖onAdvance() 方法.
 
在阶段改变之前和在唤醒 arriveAndAwaitAdvance() 方法中休眠的全部线程们之前,此方法被 phaser 调用。这个方法接收当前阶段数作为参数,0是第一个phase ,还有注册的参与者数。最有用的参数是actual phase。如果你要基于不同的当前阶段执行不同的操作,那么你必须使用选择性结构(if/else 或 switch)来选择你想执行的操作。例子里,我们使用了 switch 结构来为每个phase的改变选择不同的方法。
 
onAdvance() 方法返回 Boolean 值表明 phaser 终结与否。如果返回 false 值,表示它还没有终结,那么线程将继续执行其他phases。如果phaser 返回真值,那么phaser将叫醒全部待定的线程们,并且转移phaser到terminated 状态,所以之后的任何对phaser的方法的调用都会被立刻返回,还有isTerminated() 方法将返回真值。
 
在核心类,当你创建 MyPhaser 对象,在phaser中你不用表示参与者的数量。你为每个 Student 对象调用了 register() 方法创建了phaser的参与者的注册。这个调用不会在Student 对象或者执行它的线程与phaser之间这个建立任何关系。 说真的,phaser的参与者数就是个数字而已。phaser与参与者之间没有任何关系。

在并发任务间交换数据::::::::::::
Java 并发 API 提供了一种允许2个并发任务间相互交换数据的同步应用。更具体的说,Exchanger 类允许在2个线程间定义同步点,当2个线程到达这个点,他们相互交换数据类型,使用第一个线程的数据类型变成第二个的,然后第二个线程的数据类型变成第一个的。
 
这个类在遇到类似生产者和消费者问题时,是非常有用的。来一个非常经典的并发问题:你有相同的数据buffer,一个或多个数据生产者,和一个或多个数据消费者。只是Exchange类只能同步2个线程,所以你只能在你的生产者和消费者问题中只有一个生产者和一个消费者时使用这个类。
 
在这个指南,你将学习如何使用 Exchanger 类来解决只有一个生产者和一个消费者的生产者和消费者问题。
1. public class Producer implements Runnable{
2. private List<String> buffer;
3. private final Exchanger<List<String>> exchanger;
4. public Producer(List<String> buffer, Exchanger<List<String>> exchanger) {
5. super();
6. this.buffer = buffer;
7. this.exchanger = exchanger;
8. }
9. @Override
10. public void run() {
11. // TODO Auto-generated method stub
12. for (int i = 0; i < 10; i++) {
13. for (int j = 0; j < 10; j++) {
14. buffer.add("message"+j);
15. }
16. try {
17. buffer = exchanger.exchange(buffer);//同步点
18. } catch (Exception e) {
19. // TODO: handle exception
20. }
21. }
22. }
23. }
1. public class Consumer implements Runnable{
2. private List<String> buffer;
3. private final Exchanger<List<String>> exchanger;
4. public Consumer(List<String> buffer, Exchanger<List<String>> exchanger) {
5. super();
6. this.buffer = buffer;
7. this.exchanger = exchanger;
8. }
9. @Override
10. public void run() {
11. // TODO Auto-generated method stub
12. for (int i = 0; i < 10; i++) {
13. try {
14. buffer = exchanger.exchange(buffer);//同步点
15. } catch (Exception e) {
16. // TODO: handle exception
17. }
18. for (int j = 0; j < 10; j++) {
19. buffer.remove(0);
20. }
21. }
22. }
23. }
1. public class Core {
2. public static void main(String[] args) {
3. List<String> buffer1 = new ArrayList<>();
4. List<String> buffer2 = new ArrayList<>();
5. Exchanger<List<String>> exchanger = new Exchanger<>();
6. Producer producer = new Producer(buffer1, exchanger);
7. Consumer consumer = new Consumer(buffer2, exchanger);
8. Thread thread = new Thread(producer);
9. Thread thread2 = new Thread(consumer);
10. thread.start();
11. thread2.start();
12. }
13. }
消费者开始时是空白的buffer,然后调用Exchanger来与生产者同步。因为它需要数据来消耗。生产者也是从空白的buffer开始,然后创建10个字符串,保存到buffer,并使用exchanger与消费者同步。  
在这儿,2个线程(生产者和消费者线程)都是在Exchanger里并交换了数据类型,所以当消费者从exchange() 方法返回时,它有10个字符串在buffer内。当生产者从 exchange() 方法返回时,它有空白的buffer来重新写入。这样的操作会重复10遍。
如你执行例子,你会发现生产者和消费者是如何并发的执行任务和在每个步骤它们是如何交换buffers的。与其他同步工具一样会发生这种情况,第一个调用 exchange()方法会进入休眠直到其他线程的达到。






在使用Python来安装geopandas包时,由于geopandas依赖于几个其他的Python库(如GDAL, Fiona, Pyproj, Shapely等),因此安装过程可能需要一些额外的步骤。以下是一个基本的安装指南,适用于大多数用户: 使用pip安装 确保Python和pip已安装: 首先,确保你的计算机上已安装了Python和pip。pip是Python的包管理工具,用于安装和管理Python包。 安装依赖库: 由于geopandas依赖于GDAL, Fiona, Pyproj, Shapely等库,你可能需要先安装这些库。通常,你可以通过pip直接安装这些库,但有时候可能需要从其他源下载预编译的二进制包(wheel文件),特别是GDAL和Fiona,因为它们可能包含一些系统级的依赖。 bash pip install GDAL Fiona Pyproj Shapely 注意:在某些系统上,直接使用pip安装GDAL和Fiona可能会遇到问题,因为它们需要编译一些C/C++代码。如果遇到问题,你可以考虑使用conda(一个Python包、依赖和环境管理器)来安装这些库,或者从Unofficial Windows Binaries for Python Extension Packages这样的网站下载预编译的wheel文件。 安装geopandas: 在安装了所有依赖库之后,你可以使用pip来安装geopandas。 bash pip install geopandas 使用conda安装 如果你正在使用conda作为你的Python包管理器,那么安装geopandas和它的依赖可能会更简单一些。 创建一个新的conda环境(可选,但推荐): bash conda create -n geoenv python=3.x anaconda conda activate geoenv 其中3.x是你希望使用的Python版本。 安装geopandas: 使用conda-forge频道来安装geopandas,因为它提供了许多地理空间相关的包。 bash conda install -c conda-forge geopandas 这条命令会自动安装geopandas及其所有依赖。 注意事项 如果你在安装过程中遇到任何问题,比如编译错误或依赖问题,请检查你的Python版本和pip/conda的版本是否是最新的,或者尝试在不同的环境中安装。 某些库(如GDAL)可能需要额外的系统级依赖,如地理空间库(如PROJ和GEOS)。这些依赖可能需要单独安装,具体取决于你的操作系统。 如果你在Windows上遇到问题,并且pip安装失败,尝试从Unofficial Windows Binaries for Python Extension Packages网站下载相应的wheel文件,并使用pip进行安装。 脚本示例 虽然你的问题主要是关于如何安装geopandas,但如果你想要一个Python脚本来重命名文件夹下的文件,在原始名字前面加上字符串"geopandas",以下是一个简单的示例: python import os # 指定文件夹路径 folder_path = 'path/to/your/folder' # 遍历文件夹中的文件 for filename in os.listdir(folder_path): # 构造原始文件路径 old_file_path = os.path.join(folder_path, filename) # 构造新文件名 new_filename = 'geopandas_' + filename # 构造新文件路径 new_file_path = os.path.join(folder_path, new_filename) # 重命名文件 os.rename(old_file_path, new_file_path) print(f'Renamed "{filename}" to "{new_filename}"') 请确保将'path/to/your/folder'替换为你想要重命名文件的实际文件夹路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值