1、前言
生活中有很多多线程的案例,购票就是一个很常见的问题,如果我们编写的程序时线程不安全返回的结果将会是不可想象,手里会出现莫名奇妙的负票数!
首先多线程的运行过程我们需要了解一下:
1)、新建状态:
新建状态就是我们通过new关键字实例化出一个线程类的对象时的状态。
2)、就绪状态(可运行状态):
调用了新建状态下的线程对象的 start() 方法来启动这个线程,此时线程对象已经准备好了,该线程对象会被放入“可运行线程池”中等待CPU分配时间片段进行运行。(进入就绪状态下的线程只能等待cpu随机调度执行,这里可以使用线程优先级进行设置进程的先后运行权重,注意不是设置优先级高的就一定先执行)
3)、运行状态:
此时cup分配调用该线程执行;开始执行线程对象 run() 方法中定义的逻辑代码
4)、阻塞状态:
阻塞可分为:等待阻塞、同步阻塞、其他阻塞。
等待阻塞:当调用线程被调用了 Object.wait() 方法后会立刻释放掉自身获取到的锁并进入“等待池”进行等待,此时等待池中的线程被其他线程调用了 使用Object.notify() 或 Object.notifyAll() 方法后会唤醒“等待池”中的线程在重新获取到锁之后会转为可运行状态。(wait()和notify()/notifyAll()只能用在被synchronized包含的代码块中)
同步阻塞:
线程执行到了被 synchronized 关键字保护的同步代码时,如果此时锁已经被其他线程取走,则该线程会进入到“等锁池”,直到持有锁的那个线程释放掉锁并且自身获取到锁之后,自身会转为可运行状态。
其它阻塞:
线程中执行了 Thread.sleep() 方法进行休眠会进入阻塞状态,直到Thread.sleep()方法休眠的时间超过参数设定的时间而超时后线程会转为可运行状态。
线程ThreadA中调用了ThreadB.join()(合并线程)方法来等待ThreadB线程执行完毕,从而ThreadA进入阻塞状态,直到ThreadB线程执行完毕后ThreadA才会继续执行(可运行状态)。
5)、死亡状态:
发生未处理异常或者run方法执行完毕后的状态。死亡状态不可复生(就好比人一生只要一次生命)
3、线程不安全购票:
public class TicketThread {
public static void main(String[] args) {
TicketGrabbing ticketThread = new TicketGrabbing();
new Thread(ticketThread,"小王").start();
new Thread(ticketThread,"小伍").start();
}
}
class TicketGrabbing implements Runnable{
//票数
private int ticket = 10;
@Override
public void run() {
while(true){
if (ticket <=0){
break;
}
//延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第:"+ticket--+"票");
}
}
}
4、线程安全购票
多人购票涉及同步问题,线程同步需要:队列+锁,此时我们要引入锁机制(synchronized )
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,放在范围操作符(public等)之后,返回类型声明(void等)之前.这时,线程获得的是成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
public class TicketThread {
public static void main(String[] args) {
TicketGrabbing ticketThread = new TicketGrabbing();
new Thread(ticketThread,"小王").start();
new Thread(ticketThread,"小伍").start();
new Thread(ticketThread,"小理").start();
new Thread(ticketThread,"小子").start();
new Thread(ticketThread,"小尽快把").start();
new Thread(ticketThread,"小接口").start();
new Thread(ticketThread,"举报").start();
new Thread(ticketThread,"就赶快").start();
}
}
class TicketGrabbing implements Runnable{
//票数
private int ticket = 10;
boolean flag = true; //外部停止方式,不建议使用Thread自带的stop,dishistry
@Override
public void run() {
while(flag){
buy();
}
}
private synchronized void buy(){
if (ticket <= 0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第:"+ticket--+"票");
//延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}