一、什么叫线程的互斥
日常生活中,总会有多个火车票售卖窗口,他们可以实现多个窗口对总的车票进行售卖,而不会发生在两个或多个窗口售卖同一张车票的情况,这个处理就叫做线程的互斥处理。
例如,设有若干线程共享某个变量,而且都对变量有修改。如果它们之间不考虑相互协调工作,就会产生混乱。比如,线程A和B共用变量x,都对x执行增1操作。由于A和B没有协调,两线程对x的读取、修改和写入操作相互交叉,可能两个线程读取相同个x值,一个线程将修改后的x新值写入到x后,另一个线程也把自己对x修改后的新值写入到x。这样,x只记录后一个线程的修改作用。
即,多个线程并行的修改数据,但同一时刻,对于同一对象,只能有一个线程对该对象进行访问及修改。
二、线程的互斥与同步
1、互斥及同步的概念
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。
线程互斥:指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。为多线程的同步,提供了wait()和notify()方法。wait()方法是,当一个线程执行了这个方法时,就会放弃互斥锁,进入互斥锁的等待队列。notify()方法是,唤醒互斥锁等待队列中的进线程,进入就绪状态。
2、互斥与同步的注意事项
(1) wait()和notify()方法必须配对使用,执行wait()方法进入等待队列的线程,必须由另一线程执行notify()方法唤醒。
(2) wait()和notify()方法必须位于同步代码块中,也就是在synchronized代码块中。
(3) 在某些情况下,可以使用notifyAll()方法代替notify()方法,唤醒等待队列中的所有线程。
三、互斥对象
通常将多线程并发访问的资源称为临界资源。对临界资源的访问必须是互斥的,java可以为每一个对象设置一个“互斥锁”,保证同一时刻只有一个线程拥有互斥锁,其它线程必须等待拥有锁的线程释放后才可以获取。
java中提供了synchronize关键字去实现“互斥锁”。当定义类、方法或者代码片段时,使用该关键字,就表示和该关键字相关联的对象有互斥锁。
四、车票售卖代码
package cn.itcast_08;
public class SellTicket {
int ticketNum; //总可售卖票数
public SellTicket(int ticketNum) {
this.ticketNum=ticketNum;
}
public synchronized void sellTicket(int num) {
if(ticketNum-num>=0) {
try {
Thread.sleep(1000);
ticketNum-=num;
System.out.println("窗口:"+Thread.currentThread().getName()+"成功售卖"+num+"张票!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
System.out.println(",票量不够,票只剩下"+ticketNum+"张!");
}
}
}
package cn.itcast_08;
import java.util.Scanner;
public class SellThread extends Thread {
SellTicket ticket;
public SellThread(SellTicket ticket) {
super();
this.ticket=ticket;
}
@Override
public void run() {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in );
while(ticket.ticketNum>0) {
int num=sc.nextInt(); //键盘录入要购买的票数
ticket.sellTicket(num);
}
}
public static void main(String[] args) throws InterruptedException {
System.out.print("请输入可售卖的总票数:");
Scanner sc=new Scanner(System.in );
int allNum=sc.nextInt(); //键盘录入可卖的总票数
SellTicket ticket=new SellTicket(allNum);
SellThread t1=new SellThread(ticket);//线程1
SellThread t2=new SellThread(ticket);//线程2
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("目前剩余可售票数:"+ticket.ticketNum);
}
}
运行结果如下:
请输入可售卖的总票数:50
20
窗口:Thread-1成功售卖20张票!
2
窗口:Thread-1成功售卖2张票!
4
窗口:Thread-1成功售卖4张票!
1
窗口:Thread-1成功售卖1张票!
3
窗口:Thread-0成功售卖3张票!
4
窗口:Thread-1成功售卖4张票!
2
窗口:Thread-0成功售卖2张票!
3
窗口:Thread-1成功售卖3张票!
4
窗口:Thread-0成功售卖4张票!5
窗口:Thread-1成功售卖5张票!
6
,票量不够,票只剩下2张!2
窗口:Thread-0成功售卖2张票!
注意:wait()方法会释放互斥锁,必须通过notify()方法唤醒互斥锁等待队列的线程,而sleep()方法不会。