一、等待与通知机制、等待超时机制
前言
本文主要是讲述等待与通知机制和等待与超时机制,三者都是利用wait(),notify(),notifyAll()的使用来实现的。java中的这三个并发属性是属于对象的。所以各个线程在操作时候需要对对象的实例方法进行加锁,进而获得执行权。所以说是线程来对外方对象来进行加锁,进而改变条件,通知其他等待在该对象上的线程。举例实现等待与通知机制-数据库连接池
1.等待与通知的标准范式
1)等待方
a、获取对象的锁
b、循环里判断是否满足条件,不满足则调用wait()方法
c、条件满足则执行业务逻辑
2)通知方
a、获取对象的锁
b、改变条件
c、通知所有等待在对象上的线程
2.通知:notify和notifyAll该用那个
注视:我们现在做一个例子,设计一个快递类,有两个属性,公里数和城市。现在使用等待与通知机制,当城市变化或者公里数变化时,我们做一些业务动作。
package cn.enjoy.controller.thread;
/**
* @author:wangle
* @description:测试notify和notifyall区别类
* @version:V1.0
* @date:2020-03-14 21:27
**/
public class NotifyAndNotifyAll {
public final static String city = "shanghai";
private int km ; /** 快递运输的里程**/
private String site; /** 快递当前所在城市**/
public NotifyAndNotifyAll(){
}
public NotifyAndNotifyAll(int km,String city){
this.km = km;
this.site = city;
}
public synchronized void changeKm(){
this.km=101;
notifyAll();
}
public synchronized void changeSite(){
this.site="beijing";
notifyAll();
}
public synchronized void waitSite(){
while(city.equals(this.site)){
try {
System.out.println(Thread.currentThread().getName()+" 正在等待城市变化");
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("城市已经从:"+city+"到达了:"+this.site);
}
public synchronized void waitKm(){
while(this.km <= 100){
try {
System.out.println(Thread.currentThread().getName()+" 正在等待公里数变化");
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("快递已经距离出发点:"+this.km+"千米");
}
}
package cn.enjoy.controller.thread;
/**
* @author:wangle
* @description:测试
* @version:V1.0
* @date:2020-03-14 21:48
**/
public class TestNotifyAndNotifyAll {
private static NotifyAndNotifyAll notifyAndNotifyAll = new NotifyAndNotifyAll(0,NotifyAndNotifyAll.city);
private static class CheckKm extends Thread{
@Override
public void run(){
notifyAndNotifyAll.waitKm();
}
}
private static class CheckSite extends Thread{
@Override
public void run(){
notifyAndNotifyAll.waitSite();
}
}
public static void main(String[] args)throws InterruptedException{
for(int i=0;i<3;++i){
new CheckSite().start();
}
for(int i=0;i<3;++i){
new CheckKm().start();
}
Thread.sleep(1000);
notifyAndNotifyAll.changeKm();
}
}
使用notifyAll的结果
可以看到,当我们在改变了公里数,通知了所有的wait在该对象上的线程,并且做出了公里数改变了相应的业务。
使用notify的结果
可以看到,我们在改变了条件,使用nitify通知时,随机选中了一个等待在该对象上的线程(JVM会在等待队列中取第一个线程进行唤醒),恰巧选中了等待城市变化的线程,但是我们明明改变的是公里数,并且通知。这就是notify,只会从等待中的线程随机选取一个通知,其他的则不会收到信号。
3、等待超时模式
1)等待超时模式范式
假设需要等待的时间为T,当前时间加上T以后超时
long overtime = now + T;
long remain = T;
while(result不满足条件&&remain>0){
wait(remain);
remain = overtime-now;
}
return result;
2)实现一个数据库连接池
package cn.enjoy.controller.thread.DBPOLL;
import java.sql.Connection;
import java.util.LinkedList;
/**
* @author:wangle
* @description:数据库连接池
* @version:V1.0
* @date:2020-03-15 16:30
**/
public class DBPoll {
/**连接池大小**/
private static LinkedList<Connection> poll = new LinkedList<>();
/**初始化连接池**/
public DBPoll(int initSize){
if(initSize>0){
for(int i =0;i<initSize;++i){
poll.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/**
* @author wangle25
* @description 获取连接
* @date 16:48 2020-03-15
* @param time:超时时间
* @return java.sql.Connection
**/
public Connection getConnection(long time)throws InterruptedException{
synchronized (poll){
//永远不超时
if(time<=0){
while(poll.isEmpty()){
poll.wait();
}
return poll.removeFirst();
}else{
long overTime = System.currentTimeMillis()+time;
long remain = time;
while(poll.isEmpty()&&remain>=0){
poll.wait(remain);
remain = overTime-System.currentTimeMillis();
}
Connection result = null;
if(!poll.isEmpty()){
result = poll.removeFirst();
}
return result;
}
}
}
/**
* @author wangle25
* @description 释放链接
* @date 16:58 2020-03-15
* @param connection
* @return void
**/
public void releaseConnection(Connection connection){
synchronized (poll){
poll.addLast(connection);
poll.notifyAll();
}
}
}
package cn.enjoy.controller.thread.DBPOLL;
import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author:wangle
* @description:测试数据库连接
* @version:V1.0
* @date:2020-03-15 16:59
**/
public class DBPollTest {
static DBPoll dBPoll = new DBPoll(10);
static CountDownLatch end;
public static void main(String[] arg)throws Exception{
int threadCount = 50;
end = new CountDownLatch(threadCount);
int workCount = 20;
AtomicInteger got = new AtomicInteger();
AtomicInteger notGot = new AtomicInteger();
for(int i=0;i<threadCount;i++){
Thread thread = new Thread(new Worker(workCount,got,notGot));
thread.start();
}
end.await();
System.out.println("总共尝试了:"+threadCount*workCount+"次");
System.out.println("获取到次数:"+got);
System.out.println("未获取到次数:"+notGot);
}
static class Worker implements Runnable{
int count;
AtomicInteger got;
AtomicInteger notGot;
public Worker( int count,AtomicInteger got,AtomicInteger notGot){
this.count = count;
this.got=got;
this.notGot=notGot;
}
@Override
public void run(){
while(count>0){
try {
Connection connection = dBPoll.getConnection(1000L);
if(null != connection){
try{
connection.createStatement();
connection.commit();
}finally {
dBPoll.releaseConnection(connection);
got.incrementAndGet();
}
}
notGot.incrementAndGet();
}catch (Exception e){
}finally {
count--;
}
}
end.countDown();
}
}
}
结果
阐述:启动了50个线程、每个线程都获取数据库连接20次,有877次成功,123次失败。以上就是利用等待与通知机制实现的数据库连接池。
二、join方法
1、作用
可以控制线程执行的顺序,例如,线程A执行了线程B的join方法,则线程A需要等待线程B执行完成之后才能继续执行。下面用一段代码来验证
package cn.enjoy.controller.thread.DBPOLL.join方法;
import cn.enjoy.controller.thread.DBPOLL.SleepTools;
/**
* @author:wangle
* @description:使用join方法对线程进行串行化
* @version:V1.0
* @date:2020-03-19 15:28
**/
public class UseJoin {
static class JumpQueue implements Runnable{
private Thread thread;
public JumpQueue(Thread thread){
this.thread = thread;
}
@Override
public void run(){
try {
thread.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"run over");
}
}
public static void main(String[] args){
//取到当前主线程
Thread previous = Thread.currentThread();
for(int i=0;i<10;i++){
Thread thread = new Thread(new JumpQueue(previous),String.valueOf(i));
System.out.println(previous.getName()+"插入到了"+thread.getName()+"前面");
thread.start();
previous = thread;
}
SleepTools.second(2);
System.out.println(Thread.currentThread().getName()+"run over");
}
}
阐述:我们循环起了10个线程,当i=0的时候,主线程插到0线程的前面,当i=1,0线程插入到1线程前,依次类推。线程最终的执行顺序应该是 main->0->1->2->3->4->5->6->7->8->9
二、yield,wait,sleep,notify对锁的影响
yield和sleep后是不释放锁的
wait方法调用后会释放锁
notify方法调用后不会释放锁,等待被同步的代码块执行完成之后才会将锁释放,所以一般notify和notifyall再代码同步块的最后使用比较合理