初识java多线程的wait(), notify()方法
最近需要数据,研究了爬虫。后来发现单线程爬虫的速度不快,又研究了一下多线程。啊啊啊啊!我的文本情感分析之路什么时候才能开始呀。QAQ
本文参考了 :http://www.cnblogs.com/lwbqqyumidi/p/3817517.html
1,简介
在多线程编程的过程中,往往会涉及到线程间的通信以及公共资源的使用。为此java提供了若干方法。wait()和notify()用于进程间的通信,synchronized用于共享资源的使用权限。
本文将通过两个线程分别输出单数和双数的例子介绍wait()和 notify()方法。
2,wait()和notify()
wait()和notify()函数在上述参考博客的定义如下:
开始看不明白notify()唤醒同步锁上的其他等待线程是什么意思。看了博主给的例子也不太明白。所以自己就写了一个输出单双数的程序帮助理解。程序主要是开两个线程,共享一个变量i,当i是单数的时候,由线程A输出i的值,当i是双数的时候用线程B输出i的值。共享变量i放在一个count的class中。代码如下:
class count {
private int i;
//是否为单数
private boolean flag = true;
public count(int i) {
this.i = i;
}
//打印单数
public synchronized void print_single() throws InterruptedException {
for(int j = 0; j < 10; j++) {
if(flag) {
System.out.println(Thread.currentThread().getName() + "---" + i + ",单数," + j);
i++;
flag = false;
notify();
} else {
wait();
}
}
}
//打印双数
public synchronized void print_double() throws InterruptedException {
for(int j = 0; j < 10; j++) {
if(flag) {
wait();
} else {
System.out.println(Thread.currentThread().getName() + "---" + i + ",双数," + j);
i++;
flag = true;
notify();
}
}
}
}
类中还包含打印单数和打印双数的函数。在使用wait()和notify()函数的时候,一定要将这两个函数放在synchronized中。count类中还有个标志位flag,用于判断i是否为单数。
同时写了两个实现Runnable的线程类用于调用count类中的输出单双数的函数。代码如下:
//创建一个新线程用于调用count类中的输出单数的函数
class singleRunnable implements Runnable {
private count c;
public singleRunnable(count c) {
this.c = c;
}
public void run() {
try {
c.print_single();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//创建一个新线程用于调用count类中的输出双数的函数
class doubleRunnable implements Runnable {
private count c;
public doubleRunnable(count c) {
this.c = c;
}
public void run() {
try {
c.print_double();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
两个线程类中各有一个count的对象,在主程序中将会把同一个count对象传给这两个线程类作为两个线程类的公共资源。
最后是主函数。用于创建一个count对象和两个线程类并启动这两个线程。
public static void main(String[] args) throws IOException {
count c = new count(1); //初始化count 设置i=1
Runnable single = new singleRunnable(c);//创建一个线程类将count对象传入
Runnable Double = new doubleRunnable(c);//创建一个线程类将count对象传入
Thread tmp = new Thread(single);
Thread tmp1 = new Thread(Double);
tmp.start();//开始线程
tmp1.start();//开始线程
}
运行程序,结果如下:
咋看没有什么问题,线程1和线程2交替输出i的单双数。但是仔细看下发现程序一直没有结束还在运行,并且打印的j的值都为偶数。这就不对劲了。于是博主改变了count类中j的值,将j改成了奇数5。运行结果如下:
程序顺利执行完毕。后来又将j改成了3同样可以执行完毕。最后博主又将j改成几个偶数,结果跟第一次一样程序始终在执行。于是博主将j改成2跟随程序走了一遍发现了问题。
当两个线程启动时,i的初始值为1,flag=true。由于主函数中打印单数的线程在打印双数的线程前面进入准备状态,所以先执行print_single()函数。在print_single()中,输出i为1之后,i自增1变成2。将flag改为false,同时唤醒另一个打印双数的进程。但由于调用notify()函数后,被唤醒的线程不会马上执行,要等唤醒它的程序执行完或调用wait()后才会执行。所以print_single()函数在唤醒print_double()函数后还将继续执行,此时j自加变成1,然再判断flag,现在flag为false,所以调用wait()挂起。这时print_double()函数就开始执行了。print_double()开始执行时,j == 0,此时i==2。所以打印出的i和j分别为2,0。然后print_double()函数唤醒另一线程,同时继续执行。此时j变成1,i变成3,打印双数的线程执行wait()函数进入等待状态。等到print_single()函数开始执行时,j的值为1,从上次wait()函数停止的地方开始执行,所以j先自增变成2,判断条件不符后,结束该线程。而此时打印双数的线程任然在等待,并且由于没有notify()函数唤醒,将一直等待下去,所以就出现了我们之前看到的程序一直执行,以及打印出来的j的值都为偶数。
当我们把j的值换成奇数时,就可以正常执行完毕。
当我们在主程序中改变两个线程的启动顺序的时候即改成
public static void main(String[] args) throws IOException {
count c = new count(1, 0);
Runnable single = new singleRunnable(c);
Runnable Double = new doubleRunnable(c);
Thread tmp = new Thread(single);
Thread tmp1 = new Thread(Double);
//跟前面相比,交换了打印单双数线程的启动顺序
tmp1.start();
tmp.start();
//--------------------------------
}
j的值还是偶数时,这时程序也可以顺利执行完毕。
可以根据我分析第一种的方法,分析以上的两种情况。
3,总结
由此我们可以发现,虽说多线程给我们的感觉是同时进行的,但是程序真正执行的时候还是有顺序之分的。将两线程启动顺序改变一下就会影响输出的结果。同时我们可以看出,wait()和notify()函数在本例子中作用的是一个对象中的不同方法,借此可以理解一下“wait(),notify()函数是作用在同步锁对象的线程上”这句话的含义。
完整代码如下:
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class count {
private int i;
//是否为单数
private boolean flag = true;
public count(int i) {
this.i = i;
}
//打印单数
public synchronized void print_single() throws InterruptedException {
for(int j = 0; j < 2; j++) {
if(flag) {
System.out.println(Thread.currentThread().getName() + "---" + i + ",单数," + j);
i++;
flag = false;
notify();
} else {
wait();
}
}
}
//打印双数
public synchronized void print_double() throws InterruptedException {
for(int j = 0; j < 2; j++) {
if(flag) {
wait();
} else {
System.out.println(Thread.currentThread().getName() + "---" + i + ",双数," + j);
i++;
flag = true;
notify();
}
}
}
}
class singleRunnable implements Runnable {
private count c;
public singleRunnable(count c) {
this.c = c;
}
public void run() {
try {
c.print_single();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class doubleRunnable implements Runnable {
private count c;
public doubleRunnable(count c) {
this.c = c;
}
public void run() {
try {
c.print_double();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class RunnableTest {
public static void main(String[] args) throws IOException {
count c = new count(1);
Runnable single = new singleRunnable(c);
Runnable Double = new doubleRunnable(c);
Thread tmp = new Thread(single);
Thread tmp1 = new Thread(Double);
tmp.start();
tmp1.start();
}
}