1.资源的并发访问控制
信号量:是一种计数器,用来保护一个或者多个共享资源的访问。
如果线程要访问一个共享资源,它必须先获得信号量。如果信号量的内部计数器大于0,信号量将减1,然后允许访问这个共享资源。计数器大于0意味着有可以使用的资源,因此线程将被允许使用其中一个资源。
import java.util.concurrent.Semaphore;
public class PrintQueue {
private final Semaphore semaphore;
public PrintQueue() {
semaphore = new Semaphore(1);
}
public void printJob(Object document) {
try {
semaphore.acquire();
long duration = (long)(Math.random()*10);
System.out.println(Thread.currentThread().getName()+": PrintQueue: Printing a Job during "+duration+" seconds");
Thread.sleep(duration);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
public class Job implements Runnable{
private PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+": Going to print a job");
printQueue.printJob(new Object());
System.out.println(Thread.currentThread().getName()+": The document has been printed");
}
}
public class Main {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Job(printQueue),"Thread "+i);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
}
使用信号量实现临界区必须遵循三个步骤:- 首先,必须通过acquire()方法获得信号量
- 其次,使用共享资源执行必要的操作
- 最后,必须通过release()方法释放信号量
跟其他的类一样,Semaphore类的构造器也提供了第二个传入参数。这个参数是boolean型的,如果是false就是非公平模式,true是公平模式。acquireUninterruptibly():他其实就是acquire()方法。不过这个方法会忽略线程的中断而且不会抛出任何异常。tryAcquire():这个方法试图获得信号量。如果能获得就返回true;如果不能,就返回false,从而避开线程的阻塞和等待信号量的释放。
2.资源的多副本的并发访问控制
信号量不但可以保护单一共享资源,或者单一临界区的访问,从而使得保护的资源在同一个时间内只能被一个线程访问,也可以用来保护一个资源的多个副本,或者被多个线程同时执行的临界区。
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintQueue {
private final Semaphore semaphore;
private boolean freePrinters[];
private Lock lockPrinters;
public PrintQueue() {
semaphore = new Semaphore(3);
freePrinters = new boolean[3];
for (int i = 0; i < 3; i++) {
freePrinters[i] = true;
}
lockPrinters = new ReentrantLock();
}
public void printJob(Object document) {
try {
semaphore.acquire();
int assignedPrinter = getPrinters();
long duration = (long)(Math.random()*10);
System.out.println(Thread.currentThread().getName()+": PrintQueue: Printing a Job in Printer " +assignedPrinter+" during "+duration+" seconds");
TimeUnit.SECONDS.sleep(duration);
freePrinters[assignedPrinter] = true;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
private int getPrinters() {
int ret = -1;
try {
lockPrinters.lock();
for (int i = 0; i < freePrinters.length; i++) {
if (freePrinters[i]) {
ret = i;
freePrinters[i] = false;
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lockPrinters.unlock();
}
return ret;
}
}
打印Job和主类Main不需要改动。3.等待多个并发事件的完成
Java并发API提供了CountDownLatch类,他是一个同步辅助类。在完成一组正在其他线程中执行的操作之前,它允许线程一直等待。这个类使用一个整数进行初始化,这个整数就是线程要等待完成的操作的数目。当一个线程要等待某些操作先执行完时,需要调用await()方法,这个方法让线程进入休眠直到等待的所有操作都完成。当某一个操作完成后,他将调用countDown()方法将CountDownLatch类的内部计数器减1。当计数器变成0的时候,CountDownLatch类将唤醒所有调用await()方法而进入休眠的线程。
import java.util.concurrent.CountDownLatch;
public class Videoconference implements Runnable{
private final CountDownLatch controller;
public Videoconference(int number) {
controller = new CountDownLatch(number);
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("VideoConference: Initialization: "+controller.getCount()+" participants.");
try {
controller.await();
System.out.println("VideoConference: All the participants have come");
System.out.println("VideoConference: Let's start...");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void arrive(String name) {
System.out.println(name + " has arrived.");
controller.countDown();
System.out.println("VideoConference: Waiting for "+controller.getCount()+" participants.");
}
}
import java.util.concurrent.TimeUnit;
public class Participant implements Runnable {
private Videoconference conference;
private String name;
public Participant(Videoconference conference, String name) {
this.conference = conference;
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
long duration = (long)(Math.random()*10);
try {
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conference.arrive(name);
}
}
public class Main {
public static void main(String[] args) {
Videoconference videoconference = new Videoconference(10);
Thread thread = new Thread(videoconference);
thread.start();
for (int i = 0; i < 10; i++) {
Participant participant = new Participant(videoconference, "Participant "+i);
new Thread(participant).start();
}
}
}
CountDownLatch类有三个基本元素:
- 一个初始值,即定义必须等待的先行完成的操作的数目
- await()方法,需要等待其他事件先完成的线程调用
- countDown()方法,每个被等待的事件在完成的时候调用
- CountDownLatch机制不是用来保护共享资源或者临界区的,它是用来同步执行多个任务的一个或者多个线程
- CountDownLatch只准许进入一次。一旦CountDownLatch的内部计数器到达0,再调用这个方法将不起作用。如果要做类似的同步,必须创建一个新的CountDownLatch对象。
4.在集合点的同步
Java并发API提供了CyclicBarrier类,它也是一个同步辅助类。它允许两个或者多个线程在某个点上进行同步。CyclicBarrier类使用一个整型数进行初始化,这个数是需要在某个点上同步的线程数。当一个线程到达指定的点后,它将调用await()方法等待其他的线程。当最后一个线程调用CyclicBarrier类的await()方法时,CyclicBarrier对象将唤醒所有在等待的线程,然后这些线程将继续执行。
CyclicBarrier类有一个很有意义的改进,即它可以传入另一个Runnable对象作为初始化参数。当所有的线程都到达集合点后,CyclicBarrier类将这个Runnable对象作为线程执行。这个特性使得这个类在并行任务上可以媲美分治编程技术。
import java.util.Random;
public class MatrixMock {
private int[][] data;
public MatrixMock(int size,int length,int number) {
int counter = 0;
data = new int[size][length];
Random random = new Random();
for (int i = 0; i < size; i++) {
for (int j = 0; j < length; j++) {
data[i][j] = random.nextInt(10);
if (data[i][j] == number) {
counter++;
}
}
}
System.out.println("Mock: There are "+counter+" ocurrences of number in generated data");
}
public int[] getRow(int row) {
if ((row>=0)&&(row<data.length)) {
return data[row];
}
return null;
}
}
public class Results {
private int[] data;
public Results(int size) {
data = new int[size];
}
public void setData(int position,int value) {
data[position] = value;
}
public int[] getData() {
return data;
}
}
public class Searcher implements Runnable{
private int firstRow;
private int lastRow;
private Results results;
private MatrixMock mock;
private int number;
private CyclicBarrier barrier;
public Searcher(int firstRow, int lastRow, Results results, MatrixMock mock, int number, CyclicBarrier barrier) {
this.firstRow = firstRow;
this.lastRow = lastRow;
this.results = results;
this.mock = mock;
this.number = number;
this.barrier = barrier;
}
@Override
public void run() {
// TODO Auto-generated method stub
int counter;
System.out.println(Thread.currentThread().getName()+": Processing lines from "+firstRow+" to "+lastRow);
for (int i = firstRow; i < lastRow; i++) {
int[] row = mock.getRow(i);
counter = 0;
for (int j = 0; j < row.length; j++) {
if (row[j] == number) {
counter ++;
}
}
results.setData(i, counter);
}
System.out.println(Thread.currentThread().getName() +": Lines processed.");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Grouper implements Runnable{
private Results results;
public Grouper(Results results) {
this.results = results;
}
@Override
public void run() {
// TODO Auto-generated method stub
int finalResult = 0;
System.out.println("Grouper: Processing results...");
int[] data = results.getData();
for(int number : data) {
finalResult += number;
}
System.out.println("Grouper: Total result: "+finalResult);
}
}
public class Main {
public static void main(String[] args) {
final int ROWS = 10000;
final int NUMBERS = 1000;
final int SEARCH = 5;
final int PARTICIPANTS = 5;
final int LINES_PARTICIPANT = 2000;
MatrixMock matrixMock = new MatrixMock(ROWS, NUMBERS, SEARCH);
Results results = new Results(ROWS);
Grouper grouper = new Grouper(results);
CyclicBarrier barrier = new CyclicBarrier(PARTICIPANTS,grouper);
Searcher[] searchers = new Searcher[PARTICIPANTS];
for (int i = 0; i < PARTICIPANTS; i++) {
searchers[i] = new Searcher(i*LINES_PARTICIPANT, (i*LINES_PARTICIPANT)+LINES_PARTICIPANT, results, matrixMock, 5, barrier);
new Thread(searchers[i]).start();
}
System.out.println("Main: The main thread has finished.");
}
}
重置CyclicBarrier对象
虽然CyclicBarrier类和CountDownLatch类有很多共性,但是它们也有一定的差异。其中最重要的不同是,CyclicBarrier对象可以被重置回初始状态,并把它的内部计数器重置成初始化时的值。CyclicBarrier对象的重置,是通过CyclicBarrier类提供的reset()方法完成的。当重置发生后,在await()方法中等待的线程将收到一个BrokenBarrierException异常。
损坏的CyclicBarrier对象
CyclicBarrier对象有一种特殊的状态即损坏状态。当很多线程在await()方法上等待的时候,如果其中一个线程被中断,这个线程将抛出InterruptedException异常,其它的的等待线程将抛出BrokenBarrierException异常,于是CyclicBarrier对象就处于损坏状态了。可以用isBroken()方法判断是否处于损坏状态。