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(){
private final Semaphore semaphore;
public PrintQueue(){
//初始化信号量对象
semaphore=new Semaphore(1,true);
}
public void printJob(Object document){
try {
semaphore=new Semaphore(1,true);
}
public void printJob(Object document){
try {
//调用acquire()方法获取信号量
semaphore.acquire();
long duration=(long) (Math.random()*10);
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{
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();
}
}
}
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();
}
}
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[];
private boolean freePrinters[];
//声明一个Lock对象 用来保护对freePrintes数组的访问
private Lock lockPrinters;
private Semaphore semaphore;
public PrintQueue1(){
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();
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();
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;
}
}
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对象
private CountDownLatch countDownLatch;
public Videoconference(int number){
//初始化
countDownLatch=new CountDownLatch(number);
}
countDownLatch=new CountDownLatch(number);
}
//实现arrive方法 每一个与会者进入视频会议的时候 都要调用这个方法
public void arrive(String name){
System.out.printf("%s has arrived",name);
countDownLatch.countDown();
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;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{
System.out.printf("VideoConference:intialization: %d participants.\n",countDownLatch.getCount());
try{
//使用awite方法 等待所有的与会者到达
countDownLatch.await();
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();
}
}
}
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);
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();
}
}
}
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);
Videoconference videoconference=new Videoconference(10);
Thread threadConference=new Thread(videoconference);
//启动视频会议对象
threadConference.start();
threadConference.start();
//10个参与者 启动十个参与者线程
for(int i=0;i<10;i++){
Participant prParticipant=new Participant(videoconference, "Participant"+i);
Thread thread=new Thread(prParticipant);
thread.start();
}
}
}
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....
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方法上等待的线程