多线程在生活中很常见,书上的案例大都是窗口售票。如果是三个窗口售卖不同的票,基本是没什么问题,大家各自卖就好了。
但是卖同一种票,就会涉及到对资源的读取、修改。并发为我们带来了很多好处,同时也有不少麻烦。
实例:
package ticketSell;
/**
* 多线程售票,3个窗口售出2000张票
* @author XMY
*
*/
public class TicketWindow implements Runnable{
private int num=20;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if(num>0) {
System.out.println(Thread.currentThread().getName()+" 正在售出第 "+num+" 张票");//得到当前线程的名字
num--;
}else {
System.out.println(Thread.currentThread().getName()+" 已售空");
break;
}
}
}
}
java提供了两个方法去编写线程类。一种是继承Thread,一种是实现接口Runnable。直接说的话大部分都会使用Runnable,因为java不支持多继承,所以很常见的一种情况就是如果你使用的是Thread那么这个类就不能继承别的类了。但接口可以很多。
首先是三个窗口售卖三种不同的票
public class TicketSell {
public static void main(String[] args) {
// TODO Auto-generated method stub
TicketWindow tw1=new TicketWindow();
TicketWindow tw2=new TicketWindow();
TicketWindow tw3=new TicketWindow();
Thread t1=new Thread(tw1);
Thread t2=new Thread(tw2);
Thread t3=new Thread(tw3);
t1.start();
t2.start();
t3.start();
}
}
因为是不同的票,所以“资源”也不一样,那么就要实例化三个线程类。
所以当“资源”一样时。我们要做的是把这一种“资源”放进三个“窗口”——实例化一个线程类,创建三个线程。
public class TicketSell {
public static void main(String[] args) {
// TODO Auto-generated method stub
TicketWindow tickets=new TicketWindow();
Thread t1=new Thread(tickets);
Thread t2=new Thread(tickets);
Thread t3=new Thread(tickets);
t1.start();
t2.start();
t3.start();
}
}
这时候看输出结果,会发现有很大的问题
出现了抢票、漏票、重票。基本这功能是完全没实现。但是看看自己的代码,好像也没什么问题。其实引起问题的就是,多线程的并发。
引起并发问题的是图一中的这两句:
比如说线程1在执行完println这条命令,正打算执行第二条语句,线程2刚好进来了,这时候num还没有被线程1修改,线程2得到的num是之前的至。这就导致了两个窗口卖了同一张票。情况更糟一点三个窗口都可能卖一张票。
教材里的解决办法很直接,因为引发问题是因为这里有两个语句对不对,那我就直接把这两句改成一句,输出的同时直接减减。直接避免线程并发的可能性。因为只有一句话而已你线程再怎么复杂也插不进来吧。
public class TicketWindow implements Runnable{
private int num=20;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if(num>0) {
System.out.println(Thread.currentThread().getName()+" 正在售出第 "+num--+" 张票");
}else {
System.out.println(Thread.currentThread().getName()+" 已售空");
break;
}
}
}
}
这时候再看输出结果也没什么问题(顺序不同是因为线程并行运行抢占资源引起的)
问题看似解决了。但这个方法其实还是不可行。首先线程的抢占还是在发生,最关键的是总不能以后用线程的时候都浓缩成一句话,这是不现实的。其实发生问题的原因就是数据在访问、修改的时候,没能来的及同步。只要做好了数据同步,那么再多线程也不会出现问题。
为了实现同步,java提供了对象锁的方法。使得其他线程进入阻塞状态,强行的让其他线程等待,等第一个线程访问、修改好数据后,其他的线程才能进来。使用关键字synchronized
public class TicketWindow implements Runnable{
private int num=20;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
//同步代码块,this代表这个对象的对象锁
synchronized(this) {
if(num>0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 正在售出第 "+num--+" 张票");//得到当前线程的名字
}else {
//System.out.println(Thread.currentThread().getName()+" 已售空");
break;
}
}
}
}
}
同时也可以对整个方法使用
public class TicketWindow implements Runnable{
private int num=20;
@Override
public synchronized void run() {
// TODO Auto-generated method stub
while(true) {
if(num>0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 正在售出第 "+num--+" 张票");//得到当前线程的名字
}else {
//System.out.println(Thread.currentThread().getName()+" 已售空");
break;
}
}
}
}