信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
可以手动控制顺序:信号量保证它们调用的顺序(即先进先出;FIFO),Semaphore的实现使用AQS的状态来保存许可数量,它实现了公平和非公平两种策略
// 定义2个新号量
Semaphore semaphore = new Semaphore(4); // 定义2个新号量
/**
* 和其他的区别:
* 一般的情况:多个人多线程访问同一个账号
* 信号量的场景:多个人多线访问多个账号也没有问题,同时能解决多个人多线程访问同一个账号
* Semaphore可以控制某个资源可被同时访问的个数,其他是多个线程同时访问
*/
public void testSemaphore(){
// 创建并发访问的账户
MyCount myCount = new MyCount("95599200901215522", 10000);
// 创建一个锁对象
Lock lock = new ReentrantLock();
// 创建一个线程池
ExecutorService pool = Executors.newCachedThreadPool();
// 创建一些并发访问用户,一个信用卡,存的存,取的取,好热闹啊
UserSemaphore u1 = new UserSemaphore("张三", myCount, -4000, lock, semaphore);
UserSemaphore u2 = new UserSemaphore("张三他爹", myCount, 6000, lock, semaphore);
UserSemaphore u3 = new UserSemaphore("张三他弟", myCount, -8000, lock, semaphore);
UserSemaphore u4 = new UserSemaphore("张三老婆", myCount, 800, lock, semaphore);
// 在线程池中执行各个用户的操作
pool.execute(u1);
pool.execute(u2);
pool.execute(u3);
pool.execute(u4);
}
/**CountDownLatch
* 信用卡的用户
*/
public class UserSemaphore implements Runnable {
private String name; // 用户名
private MyCount myCount; // 所要操作的账户
private int iocash; // 操作的金额,当然有正负之分了
private Lock myLock; // 执行操作所需的锁对象
UserSemaphore(String name, MyCount myCount, int iocash, Lock myLock, Semaphore isOk) {
this.name = name;
this.myCount = myCount;
this.iocash = iocash;
this.myLock = myLock;
this.mIsOk=isOk;
}
private Semaphore mIsOk;
public void run() {
// 获取锁
// 执行现金业务
try {
if (mIsOk.availablePermits() > 0) {
System.out.println("线程" + Thread.currentThread().getName()+ "启动,进入银行,有位置立即去存钱");
} else {
System.out.println("线程" + Thread.currentThread().getName()+ "启动,进入银行,无位置,去排队等待等待");
}
mIsOk.acquire();
System.out.println("UserSynch" + name + "正在操作" + myCount + "账户,金额为" + iocash
+ ",当前金额为" + myCount.getCash());
myCount.setCash(myCount.getCash() + iocash);
System.out.println(name + "操作" + myCount + "账户成功,金额为" + iocash
+ ",当前金额为" + myCount.getCash());
mIsOk.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CountDownLatch利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。它可以代替Notify和wait的实现:整理和分析流程
每次调用 countDown() 方法, CountDownLatch 对象内部计数器减一。
当内部计数器达到0时, CountDownLatch 对象唤醒全部使用await()方法,不是Wait()方法睡眠的线程们。
CountDownLatch类有3个基本元素:
- 初始值决定CountDownLatch类需要等待的事件的数量。
- await() 方法, 被等待全部事件终结的线程调用。
- countDown() 方法,事件在结束执行后调用。
//有2个线程完成才开始某个线程
final CountDownLatch latch = new CountDownLatch(2);
public void testCountDownLatch(){
// 创建并发访问的账户
MyCount myCount = new MyCount("95599200901215522", 10000);
// 创建一个线程池
ExecutorService pool = Executors.newCachedThreadPool();
// 创建一些并发访问用户,一个信用卡,存的存,取的取,好热闹啊
UserCountDownLatchWait u1 = new UserCountDownLatchWait("张三", myCount, -4000, true, latch);
UserCountDownLatch u2 = new UserCountDownLatch("张三他爹", myCount, 6000, false, latch);
UserCountDownLatch u3 = new UserCountDownLatch("张三他弟", myCount, -8000, false, latch);
// UserCountDownLatchWait u4 = new UserCountDownLatchWait("张三老婆", myCount, 800, false, latch);
// 在线程池中执行各个用户的操作
pool.execute(u1);
pool.execute(u2);
pool.execute(u3);
// pool.execute(u4);
}
UserCountDownLatchWait(String name, MyCount myCount, int iocash, boolean isWait, CountDownLatch myLock) {
this.name = name;
this.myCount = myCount;
this.iocash = iocash;
this.myLock = myLock;
isNeedWait = isWait;
}
public void run() {
// 获取锁
// 执行现金业务
try {
if (isNeedWait) {
/** java.lang.IllegalMonitorStateException: object not locked by thread before wait()*/
System.out.print("name==" + name + "isNeedWait==" + isNeedWait);
myLock.wait();
}
System.out.println(name + "正在操作" + myCount + "账户,金额为" + iocash
+ ",当前金额为" + myCount.getCash() + isNeedWait);
myCount.setCash(myCount.getCash() + iocash);
System.out.println(name + "操作" + myCount + "账户成功,金额为" + iocash
+ ",当前金额为" + myCount.getCash() + isNeedWait);
// 释放锁,否则别的线程没有机会执行了
if (!isNeedWait) {
myLock.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
总结:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
思考:
在很多情况下,可能有多个线程需要访问数目很少的资源。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻
只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程?
答:1.给方法加同步锁,保证同一时刻只能有一个人去调用此方法,其他所有线程排队等待,但是此种情况下即使你的数据库链接有10个,也始终只有一个处于使
用状态。这样将会大大的浪费系统资源,而且系统的运行效率非常的低下。
2.另外一种方法当然是使用信号量,通过信号量许可与数据库可用连接数相同的数目,将大大的提高效率和性能。