购票问题
题目: 三个售票员 卖出 30张票
从购票问题入手,购票问题涉及到多线程并发问题,多线程同时访问卖票资源,怎么保证数据一致性是最重要的问题。
解决方案:
- 在方法上添加 synchronized 同步标志控制线程访问,一次只能有一个线程访问该方法。
- JDK1.5后提供的JUC(Java并发包)。
个人进行多线程编程按照三步骤:
在高内聚低耦合的前提下: 线程-----操作-----资源类
线程:创建线程对象
操作:调用资源类对外暴露的调用方法
资源类:线程操作的对象
Synchronized 同步
定义一个资源类,票数初始量和卖票方法,在卖票方法加上 Synchronized 同步标志。
class Ticket01{
//30张票
private int number = 30;
//卖票
public synchronized void saleTicket(){
if(number > 0){
System.out.println(Thread.currentThread().getName() + "\t卖出第" + (number--) + "票,还剩下" + number + "张");
}
}
}
Main方法
创建三个线程同时调用 Ticket01 类的卖票方法,输出结果如下:
public static void main(String[] args) {
Ticket01 ticket = new Ticket01();
//new Thread(Runable runable,String name)
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 1; i <= 40; i++){
ticket.saleTicket();
}
}
}, "A").start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 1; i <= 40; i++){
ticket.saleTicket();
}
}
}, "B").start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 1; i <= 40; i++){
ticket.saleTicket();
}
}
}, "C").start();
}
由于线程抢占资源是随机的,每次调用的结果都是不同的。可以看出线程调用是有序的,说明 Synchronized 控制线程有序访问是成功的。
JUC
定义一个资源类,票数初始量和卖票方法,通过 JUC 提供的 Lock 接口和 ReentrantLock 实现类对方法进行加锁操作。
Lock 接口是 JUC 提供的锁接口,相比 Synchronized 同步功能更加强大,提供了更灵活的结构化,主要使用的实现类是 ReentrantLock 可重入锁。
class Ticket02{
//30张票
private int number = 30;
//创建锁对象,可重入锁
Lock lock = new ReentrantLock();
//卖票
public void saleTicket(){
//加锁流程
lock.lock();
try {
if(number > 0){
System.out.println(Thread.currentThread().getName() + "\t卖出第" + (number--) + "票,还剩下" + number + "张");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
Main方法
创建三个线程同时调用 Ticket02 类的卖票方法,输出结果如下:
public static void main(String[] args) {
Ticket02 ticket = new Ticket02();
//new Thread(Runable runable,String name)
new Thread(() -> {for (int i = 1; i <= 20; i++)ticket.saleTicket();}, "A").start();
new Thread(() -> {for (int i = 1; i <= 20; i++)ticket.saleTicket();}, "B").start();
new Thread(() -> {for (int i = 1; i <= 20; i++)ticket.saleTicket();}, "C").start();
}
可以看出每个线程抢占资源后调用资源方法是有序的执行,Lock 也实现了多线程并发有序访问统一资源。