多线程之间除有互斥情况外, 还需要 同步, 通俗地理解,就是要按顺序执行。
当线程A 使用到某个对象, 而此对象 又需要线程B修改后才能符合本线程的需要。 这时线程 A 就要等待线程B 完 成修改工作。 这种线程相互等待称为线程的同步。
一 为实现同步, Java 提供wait(), notify(), notifyAll() 三个方法供线程 在临界段中使用。
1. 使用 wait() 时,使执行该方法的线程等待, 并允许 其他线程使用这个临界段。 这个方法是多态的,有以下两种形式:
wait(), 让线程一直等待, 直到被 notify() 或notifyAll() 方法唤醒
wait(long timeout) , 让线程等待到被唤醒, 或经过指定时间后结束等待.
2. 当线程使用完临界段后, 用notify() 方法通知由于想使用这个临界段而处于 等待的线程 结束等待.
notify() 方法只是通知第一个处于等待的线程,
如果某个线程在使用完临界段方法后,其他早先等待的线程都可结束等待, 重新竞争CPU, 则可以使用 notifyAll() 方法。
一个例子, 应用程序模拟一群顾客购买纪念品, 纪念品5元一个, 有人持5元, 有人持10元购买纪念品,持5元的能立即购买,持10元的,如果销售员有可找的零钱,也以立即购买, 如果没有可找的零钱,则这位顾客就得等待。 以下程序模拟顾客购买的过程
public class Example8_5 {
//这里14表示 14个纪念品
static SalesLady salesLady = new SalesLady(14, 0, 0);
public static void main(String[] args) {
//所有纪念品
int moneies[] = { 10, 10, 5, 10, 5, 10, 5, 5, 10, 5};
Thread[] threadArray = new Thread[10];
System.out.println("现在开始购买");
for (int i = 0; i < moneies.length; i++) {
threadArray[i] = new Thread(new CustomerClass(i + 1, moneies[i]));
threadArray[i].start();
}
// 等待所有线程结束
while (true) {
for (int i = 0; i < moneies.length; i++) {
if (threadArray[i].isAlive()) {
continue;
}
}
break;
}
System.out.println("购买结束");
}
}
public class CustomerClass implements Runnable {
int num, money; // 顾客序号, 钱的面值
@Override
public void run() {
System.out.println("我是" + num + "号顾客, 用 " + money
+ "元购买纪念品, 售货员说:" + Example8_5.salesLady.ruleForSale(num, money));
}
public CustomerClass(int n, int m) {
num = n;
money = m;
}
}
public class SalesLady {
int memontoes, five, ten; // 销售员纪念品数, 5、10 元张数
public synchronized String ruleForSale(int num, int money) {
// 购买过程为临界段
String s = null;
if (memontoes == 0) {
return "对不起,已售完";
}
switch (money) {
case 5:
memontoes--;
five++;
s = "给你一个纪念品,你的钱正好";
break;
case 10:
while (five < 1) {
System.out.println(num + "号顾客用10元钱购票, 发生等待!");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
memontoes--;
five -= 1;
ten++;
s = "给你一个纪念品,你给了10元,找你5元";
break;
}
notify();// 通知后面等待的顾客
return s;
}
public SalesLady(int m, int f, int t) {
memontoes = m;
five = f;
ten = t;
}
}
运行结果:
结果显示文字的顺序有些 出入,整 个流程是正确的。
请注意实现线程同步的一般原则: 如果两上或多个线程 修改同一个对象, 那么将执行 修改的操作方法用 synchronized 修饰, 使它成为临界段。
如果进入临界段的线程必须等待某个对象的状态被改变, 那么应调用 wait() 方法,反之当别一个进入临界段的线程修改了某个对象的状态后,就应该调用 notify() 方法,及时通知那些处于等待的线程。
有3种类型 的代码段能够作为临界段: 类方法, 实例方法,一个方法中的代码块。 下面的同步方法:
synchronized void myMethod() {
}
等价于
void myMethod() {
synchronized (this) {
}
}