您的位置:首页 > 编程语言 > Java开发

java并发编程之线程同步辅助类(一)

2016-12-19 20:01 393 查看

1:简介

上一篇博客讲到同步和临界区 主要说了多个并发任务共享一个资源时的同步情况 这个共享资源可以是一个对象 也可以是一个对象的属性 访问共享资源的代码块叫做临界区
基本同步机制:
1:synchronized关键字
2;Lock接口及其实现类 :如 reentrantLock,ReentrantrReadWriterLock ReadLock和ReentrantReadWriteLock WriteLock
接下来介绍如何使用更高级的同步机制来实现多线程间的同步

1.1资源的并发访问

学习如何使用java言语提供的信号量 信号量是一种计数器 用来保护一个或者多个共享资源的访问
如果线程想要访问一个共享资源 它必须先获取信号量 如果信号量的内部计数器大于0 信号量讲减少一 然后允许访问这个共享资源  计数器大于0意味着可以使用的资源 因此线程将被允许使用其中的一个资源  否则 如果信号量的计数器等于0  信号量将会把线程置入休眠直至计数器大于0  计数器等于0的时候意味着所有的共享资源已经被其他线程使用了 所以需要访问这个共享资源的线程必须等待  当线程私用完某个共享资源时 信号量必须释放 以便其他线程能够访问共享资源 访问操作将使信号量的内部计数器增加1

使用信号量来控制实现并发打印队列
创建一个打印队列

public class PrintQueue {
//声明一个信号量对象
private final Semaphore semaphore;
public PrintQueue(){
//初始化信号量对象
semaphore=new Semaphore(1,true);
}

public void printJob(Object document){
try {
//调用acquire()方法获取信号量
semaphore.acquire();

long duration=(long) (Math.random()*10);
//模拟实现文档打印过程
                        Thread.sleep(duration);
System.out.printf("%s :PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().currentThread().getName(),duration);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//释放信号量
semaphore.release();
}
}

}

创建Job类实现打印

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.printf("%s:Going to print a job\n",Thread.currentThread().getName());
printQueue.printJob(new Object());
System.out.printf("%s:The document has been printed\n",Thread.currentThread().getName());
}

}

创建主类

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintQueue printQueue=new PrintQueue();
//将工作类Job对象作为传入参数创建10个线程 因而每个线程都将发送文档到打印队列
Thread[] thread=new Thread[10];
for(int i=0;i<10;i++){
thread[i]=new Thread(new Job(printQueue),"Thread"+i);
}
for(int i=0;i<10;i++){
thread[i].start();
}
}
}

工作原理
它指出了使用信号量实现临界区必须遵守三个步骤 从而保护对共享资源的访问:
首先 必须通过acquire方法获得信号量
其次 使用共享资源执行必要的操作
最后 必须通过release方法释放信号量
例子中将1作为传入参数 所以创建的就是二进制信号量 信号量的内部计数器初始值是1 所有它只能保护一个共享资源的访问

1.2:资源的多副本的并发访问控制

信号量可以保护对单一共享资源 或者单一临界区的访问 从而使得保护的资源在同一个时间内只能被一个线程访问 然而 信号量也可以保护一个资源的多个副本 或者被多个线程同时执行的临界区

下面实现一个打印队列 它将被三个不同的打印机使用

public class PrintQueue1 {
//声明一个boolean型数组 freePrintes 用来存放打印机的状态 即空闲或正在打印
private boolean freePrinters[];
//声明一个Lock对象 用来保护对freePrintes数组的访问
private Lock lockPrinters;
private Semaphore semaphore;

public PrintQueue1(){
//初始化信号量为3
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=getPrinter();
//模拟文档的打印 然后等待一段随机的时间
long duration=(long) (Math.random()*10);
System.out.printf("%s: PrintQueue: Printing a Job in Printer %d during %d seconds\n",Thread.currentThread().getName(),assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
semaphore.release();
}
}
private int getPrinter() {
// TODO Auto-generated method stub
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) {
// TODO: handle exception
e.printStackTrace();
}finally{
lockPrinters.unlock();
}
return ret;
}

}

Job跟主类和上面的一样
原理:在构造器中使用3作为传入参数来创建信号量对象 在本例中 最开始调用acquire的方法的3个线程将获得对临界区的访问 其余的线程将被阻塞  当一个线程完成了对临界区的访问并且释放了信号量 另一个线程将获的这个信号量

1.3:等待多个并发事件的完成

java并发Api提供了CountDownLatch类 它是一个同步辅助类 在完成一组正在其他线程中执行的操作之前 它允许线程一直等待 这个类使用一个整数进行初始化 这个整数就是线程要等待完成的操作数目 当一个线程要等待某些操作先执行完时  需要调用await方法 这个方法然线程进入休眠直到等待所有的线程操作完成 当某一个线程操作完成后 它将调用ongcountDown方法将COuntDownLatch类的内部计数器减一  当计数器变成哦的时候 COuntDownLaTch类将唤醒所有调用await方法而进入休眠的线程

接下里就使用CountDownLatch实现视频会议系统 这个系统将等待所有的参会者到齐才开始

创建视频会议类

public class Videoconference implements Runnable {

//声明COuntDownLatch对象
private CountDownLatch countDownLatch;
public Videoconference(int number){
//初始化
countDownLatch=new CountDownLatch(number);
}
//实现arrive方法 每一个与会者进入视频会议的时候 都要调用这个方法
public void arrive(String name){
System.out.printf("%s has arrived",name);
countDownLatch.countDown();
//打印还没到达与会者的数目
System.out.printf("VideoConference;Writing for %d participants.\n",countDownLatch.getCount());

}

@Override
public void run() {
// TODO Auto-generated method stub
//打印出这次视频会议的人数
System.out.printf("VideoConference:intialization: %d participants.\n",countDownLatch.getCount());
try{
//使用awite方法 等待所有的与会者到达
countDownLatch.await();       
//当所有到达后 打印出到齐消息                                                   
System.out.printf("VideoConference:All the participants have come\n");
System.out.printf("VideoConference: Let.s start....\n");
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}

}

创建参加视频会议的类

public class Participant implements Runnable {

private Videoconference confVideoconference;
private String name;
public Participant(Videoconference confVideoconference,String name){
this.name=name;
this.confVideoconference=confVideoconference;
}
@Override
public void run() {
// TODO Auto-generated method stub
//随机等待一段时间
long duration=(long) (Math.random()*10);
try{
TimeUnit.SECONDS.sleep(duration);
System.out.println(duration);
//使用视频会议对象的arrive方法表明一个参与者的到来
confVideoconference.arrive(name);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}

}

创建主类

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建视频对象 它要等待10的参与者到齐
Videoconference videoconference=new Videoconference(10);
Thread threadConference=new Thread(videoconference);
//启动视频会议对象
threadConference.start();
//10个参与者 启动十个参与者线程
for(int i=0;i<10;i++){
Participant prParticipant=new Participant(videoconference, "Participant"+i);
Thread thread=new Thread(prParticipant);
thread.start();
}
}

}

运行结果如下
VideoConference:intialization: 10 participants.

2

Participant2 has arrivedVideoConference;Writing for 9 participants.

3

Participant8 has arrivedVideoConference;Writing for 8 participants.

4

Participant6 has arrivedVideoConference;Writing for 7 participants.

4

Participant4 has arrivedVideoConference;Writing for 6 participants.

6

Participant1 has arrivedVideoConference;Writing for 5 participants.

7

Participant0 has arrived7

Participant3 has arrivedVideoConference;Writing for 3 participants.

7

Participant5 has arrivedVideoConference;Writing for 2 participants.

7

VideoConference;Writing for 4 participants.

Participant7 has arrivedVideoConference;Writing for 1 participants.

8

Participant9 has arrivedVideoConference;Writing for 0 participants.

VideoConference:All the participants have come

VideoConference: Let.s start....

工作原理
COuntDownLatch类有三个基本元素:
一个初始值 即定义必须等待的先行完成的操作数目
await方法 需要等待其他完成事件先完成的线程调用
countDowen方法 每个被等待的事件在完成的时候调用
当创建COuntDownLatch对象时  使用构造器来初始化内部计数器 当countDown()方法被调用后 计数器将减一 当计数器到0的时候 CountDownLatch对象唤起所有在await方法上等待的线程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息