多线程下模拟多窗口售票
还是挺有趣的,哈哈~~~
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//模拟三个售票窗口
new Thread(ticket, "A").start();
new Thread(ticket, "B").start();
new Thread(ticket, "C").start();
}
}
//资源类
class Ticket implements Runnable{
private int num = 30;
@Override
public void run() {
while (true){
if (num > 0){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+num+"张票");
num--;
}
}
}
}
我们发现可能会出现重复的票,不存在的票,漏票,为什么呢?
CPU根据自己的喜好,在三个线程中来回的选择执行,所以是可能会出现上面的情况
如何避免呢?
方式一:使用同步代码块,锁的对象是this,也可以是任意对象。当某个线程执行到要操作资源类的步骤时,判断是否有锁,有,获取锁,没有等待其他线程释放锁
public void run() {
while (true){
synchronized (this){
if (num > 0){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+num+"张票");
num--;
}
}
}
}
方式二:同步方法,把操作资源类的步骤提取出来,成为一个方法,锁对象是this
public void run() {
while (true){
sale();
}
}
public synchronized void sale(){
if (num > 0){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+num+"张票");
num--;
}
}
方式三:使用静态代码块
public void run() {
while (true){
sale();
}
}
public static void sale(){
synchronized(Test.class){
if (num > 0){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+num+"张票");
num--;
}
}
}
方式四:使用Lock锁
在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
class Ticket implements Runnable{
private static int num = 30;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();
if (num > 0){
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在卖第"+num+"张票");
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
}
注意
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。