浅析线程间的通信(wait()、notify()的使用及编写简易连接池)
上篇博客写了synchronized关键字的使用,就着上篇的基础,再写一篇关于线程间的常用通信的使用,并写一个简易连接池作为示例。
wait()
wait()方法顾名思义,就是线程在执行到wait()方法时开始阻塞。
而wait()方法一般需要遵循如下范式:
1.获取当前对象的锁
2.判断条件是否满足
3.执行wait()方法
notify()、notifyAll()
线程阻塞后需要有其他线程通知他停止阻塞状态,停止其阻塞状态的方法为notify()和notifyAll()方法。
是用这两个方法同样需要和上面用wait()配套。
1.获取当前对象的锁
2.修改条件,使唤醒后的wait()不再进行下一次阻塞
3.执行notify()方法
示例代码如下:
public class LocalClass {
private int count = 0;
public synchronized void getCount(String id){
System.out.println("LocalClass getCount() is start...");
while(count == 0 ){
try {
System.out.println("getCount() count is :"+count);
wait();
System.out.println("LocalClass getCount is be notify...");
System.out.println(id+" do something ...");
Thread.sleep(5000);
System.out.println(id + "getCount() end ..");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void chageCount(){
count ++;
notifyAll();
try {
System.out.println("notify do something.....");
Thread.sleep(5000);
System.out.println("chageCount() end ..");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
主程序如下:
public class WnDemo {
public static void main(String[] args) throws InterruptedException {
LocalClass lc = new LocalClass();
for(int i = 0 ; i < 3 ; i++){
new Thread(){
public void run() {
lc.getCount(Thread.currentThread().getId() + "");
};
}.start();
}
Thread.sleep(5000);
lc.chageCount();
}
}
此时我们会发现,wait()和notifyAll()所在的方法synchronized锁的为同一个对象,wait()所在方法获取对象锁之后,正常来讲其他线程是无法访问该对象其他方法的。那么问题来了,如何执行notifyAll()来唤醒当前等待线程呢?
实际上当线程执行到wait()方法后,会主动释放当前已经拥有的锁,当notifyAll()所在方法获取锁后,执行到notifyAll()方法先唤醒阻塞的线程。再继续执行下面的代码,当当前方法退出后释放锁。醒来的线程会重新竞争此锁,得到后会继续wait()下面的代码。
以上代码运行结果如下:
LocalClass getCount() is start...
getCount() count is :0
LocalClass getCount() is start...
getCount() count is :0
LocalClass getCount() is start...
getCount() count is :0
notify do something.....
chageCount() end ..
LocalClass getCount is be notify...
11 do something ...
11getCount() end ..
LocalClass getCount is be notify...
12 do something ...
12getCount() end ..
LocalClass getCount is be notify...
10 do something ...
10getCount() end ..
另外,notify()方法在多线程中只能使一个等待线程唤醒。笔者试验了几次发现唤醒的线程不太可控,所以此处不做演示,今后发现可控的方法在此更新,若大家有可控的方法欢迎分享。
notify()方法可调用多次,若以上代码notifyAll()出调用3次notify(),也可将所有等待线程唤醒。
下面进入我们的示例,用上面的知识写一个简易的连接池
- 首先我们需要写一个实现类,实现Connection接口
public class SqlConnectImpl implements Connection{
public final static Connection fetchConnect(){
return new SqlConnectImpl();
}
...
}
其他继承父类的代码不赘述,任意找两个方法实现以下就好,我所实现的方法是createStatement()和commit()方法,分别在里面休眠了几毫秒。
- 编写DBPool
首先需要声明list存放链接,并初始化n个connection放入其中
private LinkedList<Connection> pool = new LinkedList<Connection>();
public DBPool(int initval){
if(initval > 0){
for(int i = 0 ; i < initval ; i ++){
pool.addLast(SqlConnectImpl.fetchConnect());
}
}
}
然后编写获取连接的方法
/**
* 获取连接
* @param timeOut 超时时间
* @return
* @throws InterruptedException
*/
public Connection getConnect(int timeOut) throws InterruptedException{
synchronized(pool){
if(timeOut < 0){
while(pool.isEmpty()){
pool.wait();
}
return pool.removeFirst();
}else{
long overTime = System.currentTimeMillis() + timeOut;
long remain = timeOut;
while(pool.isEmpty() && remain > 0){
pool.wait(remain);
remain = overTime - System.currentTimeMillis();
}
Connection conn = null;
if(pool.size() > 0){
conn = pool.removeFirst();
}
return conn;
}
}
}
最后写存放链接方法:
public void putConn(Connection conn){
if(conn != null){
synchronized (pool) {
pool.addLast(conn);
pool.notifyAll();
}
}
}
编写主逻辑方法
public class TestPool {
public static void main(String[] args) throws InterruptedException {
DBPool pool = new DBPool(10);
int initThreadNum = 50;
int count = 20;
CountDownLatch cdl = new CountDownLatch(initThreadNum);
AtomicInteger success = new AtomicInteger(0);
AtomicInteger def = new AtomicInteger(0);
for(int i = 0 ; i < initThreadNum ; i ++){
new Thread(new TestThread(count, success, def,pool,cdl)).start();
}
cdl.await();
System.out.println("总共尝试了: " + (initThreadNum * count));
System.out.println("成功数:"+success.get());
System.out.println("失败数:"+def.get());
}
}
main方法中的线程:
class TestThread implements Runnable{
int count = 0;
AtomicInteger succ;
AtomicInteger def;
DBPool pool;
CountDownLatch cdl;
public TestThread(int count, AtomicInteger succ,AtomicInteger def,DBPool pool,CountDownLatch cdl) {
this.count = count;
this.succ = succ;
this.def = def;
this.pool = pool;
this.cdl = cdl;
}
@Override
public void run() {
while(count > 0){
try {
Connection conn = pool.getConnect(1000);
if(conn != null){
conn.createStatement();
conn.commit();
pool.putConn(conn);
succ.incrementAndGet();
}else{
System.out.println(Thread.currentThread().getName() + "等待超时");
def.incrementAndGet();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
count --;
}
}
cdl.countDown();
}
}
运行结果如下:
...
Thread-44等待超时
总共尝试了: 1000
成功数:882
失败数:118
volatile关键字
volatile关键字修饰的变量保证了线程间的可见性,其在多线程下并不安全。因为volatile没办法保证对变量的操作的原子性。
具体下面的博主写的非常详细,有时间我也会总结更新。
链接如下:http://www.cnblogs.com/dolphin0520/p/3920373.html