线程同步辅助类(一)

本文介绍了Java并发编程中的几种核心机制,包括信号量用于资源访问控制、CountDownLatch用于等待多个事件完成、CyclicBarrier用于集合点同步。通过具体示例展示了如何使用这些工具解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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类还有其他两种acquire()方法:
acquireUninterruptibly():他其实就是acquire()方法。不过这个方法会忽略线程的中断而且不会抛出任何异常。
tryAcquire():这个方法试图获得信号量。如果能获得就返回true;如果不能,就返回false,从而避开线程的阻塞和等待信号量的释放。
跟其他的类一样,Semaphore类的构造器也提供了第二个传入参数。这个参数是boolean型的,如果是false就是非公平模式,true是公平模式。

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只准许进入一次。一旦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()方法判断是否处于损坏状态。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值