wait & notify 简介
-
wait & notify & notifyAll 都必须在同步块里(即被 synchronized 修饰的方法)调用,即当前线程必须拥有此对象的 monitor(即锁)
-
如果我们不从同步块中调用 wait & notify & notifyAll 方法,程序将会报 IllegalMonitorStateException 异常。
-
如果我们不从同步块中调用 wait & notify & notifyAll 方法,在竞争条件中可能会存在丢失通知的可能.以生产者消费者模式来说明一下:
1. Producer 线程测试条件(缓冲区是是否已满)并确认是否需要等待(如果发现缓冲区已满则需要等待)。这里涉及判断以及设置等待(如有需要),name必然不是原子性操作.假如不在同步块里,**那么就有可能先执行了判断,但还来不及设置等待,就执行2步骤** 2. Consumer 线程在使用缓冲区中的元素后,设置条件。 3. Consumer 线程调用 notify & notifyAll 方法; 但是这个通知不会被 Producer 接收到,因为 Producer 线程还没有等待。 4. Producer 线程调用 wait() 方法并进入等待状态。 5. 所以就丢失了通知,导致生产者消费者模式不可用. -
2和3能回答为什么 wait & notify & notifyAll 必须在同步块里(即被 synchronized 修饰的方法)调用
-
wait & notify & notifyAll 都是Object 里的方法
-
wait & notify & notifyAll 不仅仅是普通方法或同步工具,更重要的是它们是 Java 中两个线程之间的通信机制。Java 程序是天生的多线程程序.
-
wait & notify & notifyAll 的本质是基于条件对象的,而且只能由已经获得锁的线程调用。每个对象都可上锁.
-
6和7能回答为什么 wait & notify & notifyAll 都是Object 里的方法而不是 Thread 类中的方法
-
wait & notify & notifyAll 方法都是本地方法,并且为final方法,无法被重写。
-
当前线程必须拥有此对象的 monitor(即锁),才能调用某个对象的 wait 方法能让当前线程阻塞。wait 方法的调用会使当前线程释放 monitor(即锁)(这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)
-
同理,当前线程必须拥有此对象的 monitor(即锁)才能调用这个对象的 notify & notifyAll 方法,notify & notifyAll 能够唤醒一个正在等待这个对象的 monitor 的线程,如果有多个线程都在等待这个对象的 monitor,则只能唤醒其中一个线程;(notify & notifyAll 方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁)
-
调用 notifyAll() 方法能够唤醒所有正在等待这个对象的 monitor 的线程,唤醒的线程获得锁的概率是随机的,取决于cpu调度
实现简易版数据库连接池
实现思路:
- DBPool 中使用 LinkedList 充当连接池容器
- DBPool 中 实现一个取数据库连接和释放数据库连接的方法:fetchConn 和 releaseConn
- fetchConn:在mills时间内还拿不到数据库连接,返回一个null.releaseConn:释放连接
- 测试类启动50个线程去取数据库连接并进行数据库操作,最后释放连接.每个线程取20次
- 记录拿到以及拿不到连接的次数
public class DBPool {
/**
* 数据库连接池的容器
*/
private static final LinkedList<Connection> POOL = new LinkedList<>();
public DBPool(int initSize) {
if (initSize > 0) {
for (int i = 0; i < initSize; i++) {
POOL.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/**
* 在mills时间内还拿不到数据库连接,返回一个null
*
* @param mills
* @return
* @throws InterruptedException
*/
public Connection fetchConn(long mills) throws InterruptedException {
synchronized (POOL) {
if (mills <= 0) {
while (POOL.isEmpty()) {
POOL.wait();
}
return POOL.removeFirst();
} else {
long overtime = System.currentTimeMillis() + mills;
long remain = mills;
while (POOL.isEmpty() && remain > 0) {
POOL.wait(remain);
//到了这一步证明wait方法返回了,即拿到了POOL的对象锁(POOL不为空了)或者等待超时了(remain<0)
remain = overtime - System.currentTimeMillis();
}
Connection result = null;
//连接池不为空,直接拿
if (!POOL.isEmpty()) {
result = POOL.removeFirst();
}
return result;
}
}
}
/**
* 放回数据库连接
*
* @param conn
*/
public void releaseConn(Connection conn) {
if (conn != null) {
synchronized (POOL) {
POOL.addLast(conn);
POOL.notifyAll();
}
}
}
}
public class SqlConnectImpl implements Connection{
public static final Connection fetchConnection(){
return new SqlConnectImpl();
}
@Override
public void commit() throws SQLException {
SleepTools.ms(70);
}
@Override
public Statement createStatement() throws SQLException {
SleepTools.ms(1);
return null;
}
}
public class DBPoolTest {
private static DBPool pool = new DBPool(10);
/**
* 控制器:控制main线程将会等待所有Woker结束后才能继续执行
*/
private static CountDownLatch end;
public static void main(String[] args) throws Exception {
// 线程数量
int threadCount = 50;
end = new CountDownLatch(threadCount);
//每个线程的操作次数
int count = 20;
//计数器:统计可以拿到连接的线程
AtomicInteger got = new AtomicInteger();
//计数器:统计没有拿到连接的线程
AtomicInteger notGot = new AtomicInteger();
//此处由于是Demo就不用线程池了,生产环境建议还是使用线程池比较安全
//启动50个线程去拿数据库连接
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new Worker(count, got, notGot),
"worker_"+i);
thread.start();
}
end.await();// main线程在此处等待
System.out.println("总共尝试了: " + (threadCount * count));
System.out.println("拿到连接的次数: " + got);
System.out.println("没能连接的次数: " + notGot);
}
static class Worker implements Runnable {
int count;
AtomicInteger got;
AtomicInteger notGot;
Worker(int count, AtomicInteger got, AtomicInteger notGot) {
this.count = count;
this.got = got;
this.notGot = notGot;
}
@Override
public void run() {
while (count > 0) {
try {
// 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
// 分别统计连接获取的数量got和未获取到的数量notGot
Connection connection = pool.fetchConn(1000);
if (connection != null) {
try {
//此处为模拟createStatement和commit,没有实际的逻辑
connection.createStatement();
connection.commit();
} finally {
pool.releaseConn(connection);
got.incrementAndGet();
}
} else {
notGot.incrementAndGet();
System.out.println(Thread.currentThread().getName()
+"等待超时!");
}
} catch (Exception ignored) {
} finally {
count--;
}
}
end.countDown();
}
}
}