如果要完成一个系统功能,同样需要各个线程的配合,这样就少不了线程之间的通信与协作。
wait()和notify/notifyAll()都是对象上的方法
等待方:
通知方:
定义快递实体类,其中包含两个变量km和site。它们分别代表快递距离目的地的距离和快递现在所处的位置。
public class Express {
public final static String CITY = "ShangHai";
private int km;/*快递运输里程数*/
private String site;/*快递到达地点*/
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
public synchronized void changeKm(){
this.km = 101;
notifyAll();
//其他的业务代码
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public synchronized void changeSite(){
this.site = "BeiJing";
notifyAll();
}
public synchronized void waitKm(){
while(this.km<=100) {
try {
wait();
System.out.println("check km thread["+Thread.currentThread().getId()
+"] is be notifed.");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("the km is"+this.km+",I will change db.");
}
public synchronized void waitSite(){
while(CITY.equals(this.site)) {
try {
wait();
System.out.println("check site thread["+Thread.currentThread().getId()
+"] is be notifed.");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("the site is"+this.site+",I will call user.");
}
}
测试类:
public class TestWN {
private static Express express = new Express(0,Express.CITY);
/*检查里程数变化的线程,不满足条件,线程一直等待*/
private static class CheckKm extends Thread{
@Override
public void run() {
express.waitKm();
}
}
/*检查地点变化的线程,不满足条件,线程一直等待*/
private static class CheckSite extends Thread{
@Override
public void run() {
express.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);
express.changeKm();//快递距离变化
}
}
输出结果:
check km thread[16] is be notifed.
the km is101,I will change db.
check km thread[15] is be notifed.
the km is101,I will change db.
check km thread[14] is be notifed.
the km is101,I will change db.
check site thread[13] is be notifed.
check site thread[12] is be notifed.
check site thread[11] is be notifed.
更改changeKm()和changeSite()方法
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
public synchronized void changeKm(){
this.km = 101;
notify();
//其他的业务代码
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public synchronized void changeSite(){
this.site = "BeiJing";
notify();
}
测试结果:
check site thread[11] is be notifed.
notify只能唤醒处于等待队列中的一个线程。上面的结果显示notify唤醒了检查地点变化的线程 而我们希望它唤醒检查距离变化的线程 这就造成了信号丢失的现象。
使用notifyAll会唤醒所有的线程,这样就防止了信号丢失的发生。所以建议使用notifyAll()方法
使用等待超时模式实现一个数据库连接池
public class SqlConnectImpl implements Connection{
/*拿一个数据库连接*/
public static final Connection fetchConnection(){
return new SqlConnectImpl();
}
}
public class DBPool {
private static LinkedList<Connection> pool = new LinkedList<>();
//initialSize为连接池初始化容量
public DBPool(int initialSize) {
for (int i = 0; i < initialSize; i++) {
//取出数据库连接并且添加到连接池的末尾
pool.addLast(new SqlConnectImpl().fetchConnection());
}
}
//从连接池中获取连接的方法 mills为超时时长
public Connection fetchConn(long mills) throws InterruptedException {
synchronized (pool) {
//如果传入的mills<0 则永不超时 会一直等待
if (mills < 0) {
//当连接池为空时则一直等待
while (pool.isEmpty()){
pool.wait();
}
//当连接池不为空时则移除并返回第一个连接
return pool.removeFirst();
} else {
//超时的时刻
long overTime = System.currentTimeMillis() + mills;
//剩余的超时时间
long remain = mills;
//如果连接池为空,并且未到达超时时刻 则继续等待 并且更新剩余的超时时间
if (pool.isEmpty() && remain > 0) {
pool.wait(remain);
//更新剩余的超时时间
remain = overTime - System.currentTimeMillis();
}
//连接池为空并且已经到达超时时间 直接返回null
Connection result = null;
if (!pool.isEmpty()) {
//当连接池不为空时 从连接池中取出第一个连接 并且将其从池中移除
result = pool.removeFirst();
}
return result;
}
}
}
//释放数据库连接池的连接
public void releaseConn(Connection conn) {
if (conn != null) {
synchronized (pool) {
pool.addLast(conn);
//通知所有的线程 连接池中有连接了
pool.notifyAll();
}
}
}
}
测试类:
public class DBPoolTest {
static DBPool pool = new DBPool(10);
// 控制器:控制main线程将会等待所有Woker结束后才能继续执行
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();//计数器:统计没有拿到连接的线程
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;
public Worker(int count, AtomicInteger got,
AtomicInteger notGot) {
this.count = count;
this.got = got;
this.notGot = notGot;
}
public void run() {
while (count > 0) {
try {
// 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
// 分别统计连接获取的数量got和未获取到的数量notGot
Connection connection = pool.fetchConn(1000);
if (connection != null) {
try {
connection.createStatement();
connection.commit();
} finally {
pool.releaseConn(connection);
got.incrementAndGet();
}
} else {
notGot.incrementAndGet();
System.out.println(Thread.currentThread().getName()
+"等待超时!");
}
} catch (Exception ex) {
} finally {
count--;
}
}
end.countDown();
}
}
}
测试结果:
worker_48等待超时!
worker_42等待超时!
worker_43等待超时!
worker_4等待超时!
worker_4等待超时!
总共尝试了: 1000
拿到连接的次数: 155
没能连接的次数: 845
......
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将早于子线程结束。这时,如果主线程想等子线程执行完成才结束,比如子线程处理一个数据,主线程想要获得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法中如果传入参数,则表示这样的意思:如果A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
JumpQueue的构造方法能接收一个线程 该线程作为插队线程。例如:当Thread[0]创建并运行后 传入的主线程作为插队线程来调用Thread[0]的join方法 这样Thread[0]就会放弃执行权 将执行权交给主线程 以此类推创建的10个线程中Thread[9]是最后执行的。
代码演示
public class UseJoin {
//用于插队的线程
static class JumpQueue implements Runnable {
private Thread thread;//用来插队的线程
public JumpQueue(Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
//thread 代表传入的插队线程 currentThread代表现在正在运行的线程
System.out.println(thread.getName() + "插入到了线程[" + Thread.currentThread().getName() + "]的前面");
+ Thread.currentThread().getName());
//第一次插队线程main调用Thread[0]的join()方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程[" + Thread.currentThread().getName() + "]运行完成了");
}
}
public static void main(String[] args) throws Exception {
Thread previous = Thread.currentThread();//现在是主线程
for (int i = 0; i < 10; i++) {
//i=0,previous 是主线程,i=1;previous是i=0这个线程
Thread thread =
new Thread(new JumpQueue(previous), String.valueOf(i));
System.out.println(previous.getName()+" jump a queue the thread:"
+thread.getName());
thread.start();
previous = thread;
}
SleepTools.second(2);//让主线程休眠2秒
System.out.println("线程[" + Thread.currentThread().getName() + "]运行完成了");
}
}
执行结果:
0插入到了线程[1]的前面
main插入到了线程[0]的前面
1插入到了线程[2]的前面
2插入到了线程[3]的前面
3插入到了线程[4]的前面
4插入到了线程[5]的前面
5插入到了线程[6]的前面
6插入到了线程[7]的前面
7插入到了线程[8]的前面
8插入到了线程[9]的前面
线程[main]运行完成了
线程[0]运行完成了
线程[1]运行完成了
线程[2]运行完成了
线程[3]运行完成了
线程[4]运行完成了
线程[5]运行完成了
线程[6]运行完成了
线程[7]运行完成了
线程[8]运行完成了
线程[9]运行完成了
线程在执行yield()以后,持有的锁是不释放的
sleep()方法被调用以后,持有的锁是不释放的
调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放(如果不释放锁 那么notify就拿不到锁),当wait方法返回的时候,线程会重新持有锁
调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的