使用两个线程实现两个窗口卖票,不能卖出重复的票,两个线程使用同一个数据
结果图
1.先建立一个共享的数据存储,这里用ArrayList来实现
package com.leiyustudy.thread.safe;
import java.util.ArrayList;
/**
*
*
*/
public class TicketService {
private ArrayList<String> all;
public TicketService() {
all =new ArrayList<>();
all.add("01车01A");
all.add("01车01B");
all.add("01车01C");
all.add("01车01D");
all.add("01车01F");
all.add("02车02A");
all.add("02车02B");
all.add("02车02C");
all.add("02车02D");
all.add("02车02F");
}
//查询是否还有票
public boolean hasTicket() {
return all.size()>0;
}
//卖票
public String sale() {
return all.remove(0);
}
/**
* @param args
*/
}
2.创建一个类来继承Thread类实现多线程卖票
/**
*
*/
package com.leiyustudy.thread.safe;
/**
* @author 雷雨
*同时两个人卖票
*两个线程使用了共享的同一数据,就可能有线程安全问题.
*如何判断
*(1)是否有多个线程共享一个数据
*(2)是否有多个线程共同操作和访问同一个数据
*
*如何解决? 加锁
*同步:
*(1)同步的锁对象可以是任意类型的对象(意思就是不限制它的类型,只有共享的人都默认承认这个锁的存在就可以
*(2)使用共享数据的多个线程承认这个锁
*
*两种形式;
*(1)同步代码块
*语法格式:
*synchronized(需要同步的锁对象){
* 需要锁着的代码:一个线程在运行代码期间不想别的线程加入进来,
*}
*
*(2)同步方法
*/
public class TestTicket {
/**
* @param args
*/
public static void main(String[] args) {
// TODO 自动生成的方法存根
Ticketsaler t1 = new Ticketsaler("窗口一");
Ticketsaler t2 = new Ticketsaler("窗口二");
t1.start();
t2.start();
}
}
class Ticketsaler extends Thread{
private static TicketService ts = new TicketService();
public Ticketsaler (String name) {
super(name);
}
public void run() {
//这里锁的的ts对象时两个线程公用的对象,因为他是static的对象,但是同时也导致了锁着的ts对象(票库中的票全是窗口一线程卖出的)
while(true) {
synchronized (ts) {
if(ts.hasTicket()) {
try {
Thread.sleep(1000);
//这里用synchronized来锁,在sleep的过程中不释放锁对象
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(getName()+"卖出了"+ts.sale());
}
else {
break;
}
}
}
}
}
3.创建一个类来实现Runnable接口来实现多线程卖票
/**
*
*/
package com.leiyustudy.thread.safe;
import org.junit.runner.notification.RunListener.ThreadSafe;
/**
* @author 雷雨
*
*
*继承Thread和实现Runnable的区别:
*(1)共享对象
*Thread继承来做的话必须用静态的对象
*而Runnable来做只需要得到共同的Runnable的对象,而不一定要静态对象,静态对象的生命周期很长,不建议大量使用
*(2)选择锁对象时比较方便
*因为在Runnable的run方法中的对象就是Runnable的同一个对象,所以可以用this来锁对象
*而继承Thread的方法就不能用this来锁
*(3)继承有单继承限制,实现没有限制
*在java程序开发中尽量面向接口编程
*
*/
public class TestTicketRunnable {
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable();
Thread t1 = new Thread(r1,"窗口一");
Thread t2 = new Thread(r1,"窗口二");
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable{
private TicketService ts = new TicketService();
@Override
public void run() {
while(true) {
synchronized (ts) {
if(ts.hasTicket()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出了"+ts.sale());
}
else {
break;
}
}
}
}
}
继承Thread类和实现Runnable接口来实现多线程的方式不同,对于实现Runnable接口的方式来说,对于选择锁的对象比较简单,但是线程的启动得依靠Thread对象来启动.总的来说,java尽量面向接口编程.