Semaphore是什么?
Semaphore是Java提供的同步对象类,实现了经典的信号量,信号量是通过计数器控制对共享资源的访问。如果计数器大于0,允许访问资源,为0则拒绝访问。计数器计数允许共享资源的许可证,所以要访问资源线程必须保证获取信号量的许可证。通常,要使用信号量需要去访问共享资源的线程得尝试取得许可证,如果信号量的计算器大于0,就说明线程取得了许可证,这会导致信号量计数减小,否则,线程会被阻塞,直到能够获取许可证为止。 如果线程不需要访问共享资源时需要释放许可证。
Semaphore类有下面两个构造方法:
Semaphore(int num)
Semaphore(int num,boolean how)
其中,num指定了初始许可证的计数大小,比如num=1则表示任意时刻只有一个线程能够访问资源。默认情况下,等待线程以未定义的顺序获得许可证。通过将how设置为true,可以确定等待线程以它们要求的访问的顺序获得许可证。
得到许可证可以通过调用acquire()方法,该方法有如下两张方式:
void acquire() throws InterrupetedException
void acquire(int num) throws InterrupetedException
不传num表示获取一个许可证,传则表示获取num个许可证,
释放许可证可以调用release()方法,如下:
void release()
void release(int num)
线程在使用信号量的时候,必须先调用acquire() ,当线程使用完资源后必须调用release(),具体看下面demo:
public class SemaphoreTest {
//共享资源类
static class Shared{
static int count = 0;
}
static class ThreadOne implements Runnable{
String name;
Semaphore semaphore;
public ThreadOne(String name,Semaphore sem) {
this.name = name;
this.semaphore = sem;
new Thread(this).start();
}
@Override
public void run() {
System.out.println("开始:"+name);
try {
System.out.println(name + "等待获取一个许可证");
semaphore.acquire();
System.out.println(name + "获取了一个许可证");
for (int i = 0; i <5 ; i++) {
Shared.count++;
System.out.println(name+ ":"+Shared.count);
Thread.sleep(10);
}
}catch (Exception e){
}
System.out.println(name + "释放许可证");
semaphore.release();
}
}
static class ThreadTwo implements Runnable{
String name;
Semaphore semaphore;
public ThreadTwo(String name,Semaphore sem) {
this.name = name;
this.semaphore = sem;
new Thread(this).start();
}
@Override
public void run() {
System.out.println("开始:"+name);
try {
System.out.println(name + "等待获取一个许可证");
semaphore.acquire();
System.out.println(name + "获取了一个许可证");
for (int i = 0; i <5 ; i++) {
Shared.count--;
System.out.println(name+ ":"+Shared.count);
Thread.sleep(10);
}
}catch (Exception e){
}
System.out.println(name + "释放许可证");
semaphore.release();
}
}
}
打印结果:
开始:线程One
线程One等待获取一个许可证
线程One获取了一个许可证
开始:线程Two
线程Two等待获取一个许可证
线程One:1
线程One:2
线程One:3
线程One:4
线程One:5
线程One释放许可证
线程Two获取了一个许可证
线程Two:4
线程Two:3
线程Two:2
线程Two:1
线程Two:0
线程Two释放许可证
使用信号量实现生产者与消费者
一般情况下多线程间的同步协作会使用synchronized关键字,并且结合wait()与notify()来实现同步,如下代码实现:
public class ThreadTest { static class Queue{ int count; boolean valueSet ; synchronized int get(){ while (!valueSet){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费:"+ count); valueSet = false; notify(); return count; } synchronized void put(int count){ while (valueSet){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.count = count; System.out.println("生产:"+ count); valueSet = true; notify(); } } static class Consumer implements Runnable{ Queue q; public Consumer(Queue q) { this.q = q; new Thread(this,"消费者").start(); } @Override public void run() { for (int i = 0; i < 10; i++) { q.get(); } } } static class Product implements Runnable{ Queue q; public Product(Queue q) { this.q = q; new Thread(this,"生产者:").start(); } @Override public void run() { for (int i = 0; i < 10; i++) { q.put(i); } } } public static void main(String[] args) { Queue q = new Queue(); new Product(q); new Consumer(q); } }
输出结果:生产:0
消费:0
生产:1
消费:1
生产:2
消费:2
生产:3
消费:3
生产:4
消费:4
....
使用信号量实现代码如下:
public class SemaphoreTest { static class Queue{ int count; Semaphore conSem = new Semaphore(0); Semaphore proSem = new Semaphore(1); void get(){ try { conSem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费:"+ count); proSem.release(); } void put(int count){ try { proSem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } this.count = count; System.out.println("生产:"+ count); conSem.release(); } } static class Consumer implements Runnable{ Queue q; public Consumer(Queue q) { this.q = q; new Thread(this,"消费者").start(); } @Override public void run() { for (int i = 0; i < 10; i++) { q.get(); } } } static class Product implements Runnable{ Queue q; public Product(Queue q) { this.q = q; new Thread(this,"生产者:").start(); } @Override public void run() { for (int i = 0; i < 10; i++) { q.put(i); } } } public static void main(String[] args) { Queue q = new Queue(); new Product(q); new Consumer(q); } }
打印的结果和上面一样。
注意:conSem初始化时没有设置许可证,这样可以保证首页执行put()。