java中的线程同步

来源:互联网 发布:荷兰殖民地知乎 编辑:程序博客网 时间:2024/04/29 12:54

我们在线程同步机制(一)--Synchronized和Lock简要介绍中学习了同步和临界区的概念,并且讨论了多个并发任务共享一个资源时的同步情况。访问共享资源的代码块叫临界区。

    我们在线程同步机制(一)--Synchronized和Lock简要介绍中学习了一下内容:

  • synchronized关键字

  • Lock接口及其实现类,如ReentrantLock,ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock

    本章我们将学习如何使用更高级的同步机制来实现多线程间的同步。

  •     信号量(Semaphore):一种计数器,用来保护一个或多个共享资源的访问。它是并发编程的一个基础工具。

  •     CountDownLatch:是Java语言提供的同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许线程一直等待

  •     CyclicBarrier:Java语言提供的同步辅助类,允许多个线程在某个集合点处进行相互等待

  •     Phaser:它把并发任务分多个阶段运行,在开始下一个阶段之前,当前阶段中所有的线程都必须执行完成。

  •      Exchanger:它提供了两个线程之间的数据交换点。

在应用程序中,任何时候都可以使用Semaphore来保护临界区,因为它是一个基础的同步机制。

    1、资源的并发访问控制。

        Java语言提供了信号量(Semaphore)机制。信号量是一种计数器,用来保护一个或多个共享资源的访问。如果线程要访问一个共享资源,它必须要获得信号量。如果信号量的内部计数器大于0,信号量将减1,然后允许访问这个共享资源。计数器大于0意味着有可以使用的资源,因此线程被允许使用其中一个资源。否则,如果信号的计数器等于0,信号量就会把线程置入休眠直到计数器大于0.计数器等于0的时候意味着所有的共享资源已经被其他线程使用了,所以需要访问这个共享资源的线程必须等待。

        当线程使用完某个共享资源时,信号量必须释放,以便其他线程能够访问共享资源。释放操作将使信号量内部计数器增加1.

    使用信号量实现临界区必须遵循3个步骤,从而保护对资源的访问。

        (1).必须通过acquire()方法获得信号量

        (2).使用共享资源执行必要的操作

        (3).必须通过release()方法释放信号量

    2、等待多个并发事件的完成

    Java并发API提供了CountDownLatch类。在完成一组正在其他线程中执行的操作之前,它允许线程一直等待。这个类使用一个整数进行初始化,这个整数是线程要等待完成的操作的数目。当一个线程要等待某些操作先完成时,需要调用await()方法。当一个线程操作完成后,它将调用countDown()方法使内部计数器减1.当计数器变成0的时候,CountDownLatch类将唤醒所有调用await()而进入休眠的线程

    CountDownLatch类有三个基本元素:

  • 一个初始值,即定义必须等待的先行完成的操作数据;

  • await()方法,需要等待其他事件先完成的线程调用

  • countDown()方法,每个被等待的事件在完成操作后调用,使内部计数器减1.当内部计数器到达0时,countDownLatch对象将唤醒所有在await()方法上等待的线程

    3、并发阶段任务的运行

    Java并发API还提供了Phaser,它允许执行并发多阶段任务。当我们有并发任务并且需要分解成几步执行时,这种机制就非常实用。Phaser类机制是在每一步结束的位置对线程进行同步,当所有线程都执行完了这一步,才允许执行下一步。Phaser类提供了onAdvance()方法,它在phaser阶段改变的时候自动执行。下面我们将通过一个文件查找示例来演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package org.test.concurrency.phaser;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 * Title: FileSearch.java
 * </p>
 * <p>
 * Description:
 * </p>
 * <p>
 * Copyright: Copyright (c) 2016年6月11日
 * </p>
 * <p>
 * Company:
 * </p>
 * Created on 2016年6月11日 上午8:32:06
 *
 * @author kucs FileSearch
 
 * Description:创建文件查找类,实现runnable结构。
 * 他将在一个文件夹及其子文件夹中查找过去24小时内修改过的指定扩展名的文件。
 * -----------------------------------------------------
 *
 */
public class FileSearch implements Runnable {
 // 存储要查找额文件夹
 private String initPath;
 // 存储要查找的文件的扩展名
 private String end;
 // 存储查找到的文件的完整路径
 private List<String> results;
 // 声明一个Phaser私有变量,用来控制任务不同阶段的同步
 private Phaser phaser;
 public FileSearch(String initPath, String end, Phaser phaser) {
  this.initPath = initPath;
  this.end = end;
  this.results = new ArrayList<>();
  this.phaser = phaser;
 }
 /**
  * 实现辅助方法,他们将用于run方法中
  
  */
 /**
  * directoryProcess,用于处理所有文件和文件夹
  
  * @param file
  */
 public void directoryProcess(File file) {
  File[] list = file.listFiles();
  if (list != null && list.length > 0) {
   for (File f : list) {
    if (f.isDirectory()) {
     directoryProcess(f);
    else {
     fileProcess(f);
    }
   }
  }
 }
 /** 用于查找这个传入的文件的扩展名是不是我们指定的,如果是,文件的绝对路径将被加入结果集中 */
 private void fileProcess(File f) {
  // TODO Auto-generated method stub
  if (f.getName().endsWith(end)) {
   results.add(f.getAbsolutePath());
  }
 }
 /** 对第一个阶段查找到的文件列表进行过滤,将不是24小时修改过的文件删除。 */
 public void filterResults() {
  List<String> newResult = new ArrayList<>();
  long actualDate = new Date().getTime();
  for (String strFilePath : results) {
   File file = new File(strFilePath);
   long fileDate = file.lastModified();
   if (actualDate - fileDate < TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS)) {
    newResult.add(strFilePath);
   }
  }
  results = newResult;
 }
 /** 将在第一个阶段和第二个阶段结束的时候用来检查结果集是不是空的 */
 private boolean checkResults() {
  if (results.isEmpty()) {
   System.out.printf("%s: Phaser %d: 0 results.\n", Thread.currentThread().getName(), phaser.getPhase());
   System.out.printf("%s: Phaser %d: End.\n", Thread.currentThread().getName(), phaser.getPhase());
   phaser.arriveAndDeregister();
   return false;
  else {
   System.out.printf("%s: Phaser %d: results.\n", Thread.currentThread().getName(), phaser.getPhase(),
     results.size());
   phaser.arriveAndAwaitAdvance();
   return true;
  }
 }
 /** 将结果集元素打印到控制台 */
 private void showInfo() {
  for (String strFilePath : results) {
   File file = new File(strFilePath);
   System.out.printf("%s: %s\n", Thread.currentThread().getName(), file.getAbsolutePath());
  }
  phaser.arriveAndAwaitAdvance();
 }
 @Override
 public void run() {
  // TODO 调用Phaser对象的arriveAndAwaitAdvance()方法,使查找工作在所有线程都被创建之后再开始。
  phaser.arriveAndAwaitAdvance();
  System.out.printf("%s: Starting.\n", Thread.currentThread().getName());
  File file = new File(initPath);
  if (file.isDirectory()) {
   directoryProcess(file);
  }
  // 检查结果集是不是空的,如果是空的,结束对应线程,并使用return返回
  if (!checkResults()) {
   return;
  }
  // 对结果集进行过滤
  filterResults();
  // 检查结果集是不是空的,如果是空的,结束对应线程,并使用return返回
  if (!checkResults()) {
   return;
  }
  //将结果打印到控制台,撤销线程的注册,然后将线程完成信息打印到控制台
  showInfo();
  phaser.arriveAndDeregister();
  System.out.printf("%s: Work completed.\n",Thread.currentThread().getName());
 }
}
 
package org.test.concurrency.phaser;
import java.util.concurrent.Phaser;
public class PhaserMain {
 public static void main(String[] args) {
  // TODO 创建一个Phaser对象,并指定参与阶段同步的线程是3个
  Phaser phaser = new Phaser(3);
  //定义要查到的文件目录,后缀
  FileSearch system = new FileSearch("C:\\Windows""log", phaser);
  FileSearch apps = new FileSearch("C:\\Program Files""log", phaser);
  FileSearch document = 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 documentThread = new Thread(document, "Document");
  documentThread.start();
   
  try {
   systemThread.join();
   appsThread.join();
   documentThread.join();
  catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
   
  System.out.println("Terminated: "+phaser.isTerminated());
 }
}

运行结果:

wKioL1dbZg6gdgcBAADWXRcy-Xc116.png-wh_50    

    Phaser构造器传入了参与阶段同步的线程的个数。在这个例子中,Phaser有三个参与线程。这个数字通知Phaser在唤醒所有休眠线程之前,必须执行arriveAndAwaitAdvance()方法的线程数。在Phaser创建之后,我们使用三个不同的文件查找对象创建了三个线程并启动他们。

    Phaser类提供的方法的简要介绍

  •     arrive():这个方法通知phaser对象一个参与者已经完成了当前阶段,java培训但是它不应该等待其他参与者都完成当前阶段。它不会与其他线程同步。

  •      awaitAdvance(int phase):如果传入的阶段参数与当前阶段一直,这个方法会将当前线程置入休眠,知道这个阶段的所有 参与者都运行完成。如果传入的阶段参数与当前阶段不一致,这个方法就立即返回。

  •      awaitAdvanceInterruptibly(int phaser):这个方法跟awaitAdvance(int phase)一个样,不同之处是,如果在这个方法中休眠的线程被中断,它将抛出InterruptedException。

    将参与者注册到Phaser中。

    创建一个Phaser对象时,需要指出有多少参与者,Phaser类提供了两种方法增加注册者的数量。

  • register():这个方法将一个新的参与者注册到Phaser中,这个新的参与者将被当成执行本阶段的线程。

  • bulkRegister(int phaser):这个方法将指定数目的参与者注册到Phaser

Phaser提供了一种方法减少注册者的数目,即arriveAndDeregister()。它通知phaser对象对应的线程已经完成当前阶段,并且它不会参与到下一个阶段。

4、并发任务之间的数据交换Exchanger。它允许在并发任务之间的数据交换,具体来说,Exchanger类允许两个线程定义同步点。当两个线程都达到同步点时,他们交换数据结构。

0 0
原创粉丝点击