在使用多线程时我们会发现有时候线程中的计数会出现问题
比如创建一个生产者包子加工厂和一个消费者顾客,当加工3个包子的时候加工厂停止加工。在我们为加工厂开启一个线程的时候,程序是没有问题的。
下面是各个部分代码以及开启一个线程的运行结果
//包子加工厂对象代码块
public class BaoZiMarket implements Runnable {
//创建包子参数
private BaoZi bao;
//定义count变量用于改变包子类型
int count = 0;
//定义一个包子生产的数量
int bNum = 3;
//创建带参构造函数
public BaoZiMarket(BaoZi bao) {
this.bao = bao;
}
@Override
public void run() {
//创建一个死循环用来保证程序一直运行
while (true) {
//创建同步代码块
synchronized (bao) {
// bNum--;
if(bNum<=0){
break;
}
//判断包子的状态
if (bao.flag == true) {
//如果有包子则线程进入等待状态 注意这里调用wait方法的是锁对象
try {
bao.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 报出线程名字
System.out.println(Thread.currentThread().getName());
// 线程激活状态下开始生产两种包子
if (count % 2 == 0) {
bao.pi = "冰皮";
bao.xian = "豆沙";
System.out.println("生产了" + bao.pi + bao.xian + "的包子" + "第" + bNum);
// 包子生产完毕改变包子状态
bao.flag = true;
} else {
bao.pi = "面皮";
bao.xian = "牛肉";
System.out.println("生产了" + bao.pi + bao.xian + "的包子" + "第" + bNum);
// 包子生产完毕改变包子状态
bao.flag = true;
}
count++;
bNum--;
// 设定生产时间 每一秒生产一个
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开启顾客线程
bao.notify();
}
}
}
}
顾客代码块
//顾客代码块
public class EatPeople extends Thread{
private BaoZi bao;
public EatPeople(BaoZi bao) {
this.bao = bao;
}
@Override
public void run() {
//创建死循环
while(true){
synchronized(bao){
if(bao.flag==false)
{
try {
bao.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在吃掉"+bao.pi+bao.xian+"的包子");
bao.flag=false;
bao.notify();
System.out.println("包子已经卖光了");
System.out.println("<----------------------------------->");
}
}
}
}
生产类包子对象
public class BaoZi {
String pi;
String xian;
boolean flag = false;
public BaoZi() {
}
public BaoZi(String pi, String xian) {
this.pi = pi;
this.xian = xian;
}
public String getPi() {
return pi;
}
public void setPi(String pi) {
this.pi = pi;
}
public String getXian() {
return xian;
}
public void setXian(String xian) {
this.xian = xian;
}
}
运行主类
public class CallUpMake {
public static void main(String[] args) {
BaoZi bao=new BaoZi();
BaoZiMarket bm=new BaoZiMarket(bao);
Thread t1=new Thread(bm);
// Thread t2=new Thread(bm);
EatPeople ep=new EatPeople(bao);
t1.start();
// t2.start();
ep.start();
}
}
运行结果
由上可见我们成功实现了预计目标,但是将运行主类的加工厂第二个线程注释打开会发现程序会在有线程锁的情况下超出应有执行次数
造成这种原因是因为调用了wait方法,wait方法看起来只是和sleep方法一样让线程停了下来。而实际的运行情况并不相同,下文会将sleep方法和wait方法区别进行解释,同时也会说明为什么开启两个工厂线程的时候会导致线程安全问题
sleep:
sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了 sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。
但在 sleep 的过程中过程中有可能被其他对象调用它的 interrupt() ,产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有 finally 语句块)以及以后的代码。
wait:
wait 属于 Object 的成员方法,一旦一个对象调用了wait方法,必须要采用 notify() 和 notifyAll() 方法唤醒该进程;
如果线程拥有某个或某些对象的同步锁,那么在调用了 wait() 后,这个线程就会释放【这种情况会导致我们设置的线程锁被打破,也就是为什么上述的包子加工厂开启两个线程会出现线程安全的原因】它持有的所有同步资源,而不限于这个被调用了 wait() 方法的对象。
wait() 方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt() 方法而产生 。
sleep和wait方法的区别是:
sleep 来自 Thread 类,而 wait 来自 Object 类
sleep 方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或方法
wait,notify和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用
sleep 必须捕获异常,而 wait , notify 和 notifyAll 不需要捕获异常。