多线程的实现
//100张电影票,三个窗口售票
//三个窗口售票,互不影响
//三个窗口共同出售这100张电影票
//通过线程的实现方式一 和 线程的实现方式二 售票效果的比较,得出的结论 :实现方式二比实现方式一更容易实现多线程的数据
public class Day80301 {
public static void main(String[] args) {
// //创建3个SalesWindow对象,表示三个线程,模拟三个售票窗口
// SalesWindow w1 = new SalesWindow();
// SalesWindow w2 = new SalesWindow();
// SalesWindow w3 = new SalesWindow();
//
// //启动三个线程,开始售票
// w1.start();
// w2.start();
// w3.start();
SalesTask salesTask = new SalesTask();
Thread thread1= new Thread(salesTask);
Thread thread2 = new Thread(salesTask);
Thread thread3 = new Thread(salesTask);
thread1.start();
thread2.start();
thread3.start();
}
}
//线程的第一种实现方式
class SalesWindow extends Thread{
//表示100张票
int tickets = 100;
@Override
public void run() {
while (tickets>0){
//输出语句,模拟售出一张票
System.out.println(getName()+":售出了第"+tickets-- + "张票");
}
}
}
class SalesTask implements Runnable{
int tickets = 100;
@Override
public void run() {
while (tickets>0){
System.out.println(Thread.currentThread().getName()+":售出了第:"+tickets--+"张票");
}
}
}
//当更加真实的模拟,售票场景,即增加了售票延迟之后,我们发现了两个问题
//1.售出了同一张票(多卖问题)
//Thread-2:售出了第:13张票
//Thread-1:售出了第:13张票
//Thread-0:售出了第:13张票
//2.售出了不存在的票(超卖问题)
//其实不管是多卖,还是超卖,他们都属于多线程的数据安全问题。
// 在多线程环境下,多线程访问共享数据时,访问到不正确的共享数据的值
public class Day80302 {
public static void main(String[] args) {
// //创建3个SalesWindow对象,表示三个线程,模拟三个售票窗口
// SalesWindow1 w1 = new SalesWindow1();
// SalesWindow1 w2 = new SalesWindow1();
// SalesWindow1 w3 = new SalesWindow1();
//
// //启动三个线程,开始售票
// w1.start();
// w2.start();
// w3.start();
SalesTask1 salesTask = new SalesTask1();
Thread thread1= new Thread(salesTask);
Thread thread2 = new Thread(salesTask);
Thread thread3 = new Thread(salesTask);
thread1.start();
thread2.start();
thread3.start();
}
}
//线程的第一种实现方式
class SalesWindow1 extends Thread{
//表示100张票
static int tickets = 100;
@Override
public void run() {
while (tickets>0){
//输出语句,模拟售出一张票
System.out.println(getName()+":售出了第"+tickets-- + "张票");
}
}
}
class SalesTask1 implements Runnable{
int tickets = 100;
@Override
public void run() {
//假设当前tickets的值是1
// thread3 :当前tickets的值是1, 1>0 ,thread3 进入循环准备卖票,此时发生了线程切换
// thread2 : 当前tickets的值是1, 1>0, thread2进入循环准备卖票,此时发生了线程切换
// thread1 : 当前tickets的值是1, 1>0,thread1 进入循环准备卖票
while (tickets>0){
//每次售票的延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当前tickets的值是99
//多卖问题 :前提:tickets--: 1). 读取tickets 变量的值
// 2). 减一 tickets - 1 3) tickets = tickets - 1
//thread2 :拼接字符串, thread2: 售出了第97 若此时发生了线程切换
//thread3 : 拼接字符串: thread3: 售出了第97, 此时发生了线程切换
//thread1 : 拼接字符串窜 :thread1:售出了第97张票
System.out.println(Thread.currentThread().getName()+":售出了第:"+tickets--+"张票");
}
}
}
//1.多线程运行环境
//2.多线程访问共享数据
//3.共享数据访问的非原子操作。
// 原子操作通常是指这样一组操作: 要么一组操作一次完成,要么一个操作都不做,即一组不可分割的操作
//如何解决? 只要打破上述任意条件即可
// 1. 多线程条件,无法打破(需求决定)
//2. 访问共享数据条件,(需求决定,无法打破)
//3.共享数据的费原子操作,可以打破
// 所以解决多线程数据安全问题 —> 如何将一组对共享变量的访问,变成原子操作的问题
// 思路1 : 阻止线程切换,(是由操作系统控制的,但是我们自己无法控制操作系统,抢占式线程调度)此思路无法实现
// 思路2 : 给共享变量加一把锁,利用锁来实现原子操作
// a.只有加锁的线程,才能够访问到共享变量
// b.不完成对共享变量的操作,不会释放锁
// c.只要不释放锁,其他线程就无法访问到共享变量
//synchronized (锁对象){
// 将需要作为原子操作的一组操作,放入synchronized代码块中,(需要同步的代码块)
//这一组操作就变成了原子操作
// }
public class Day80303 {
public static void main(String[] args) {
SalesTask2 salesTask1 = new SalesTask2();
Thread thread1= new Thread(salesTask1);
Thread thread2 = new Thread(salesTask1);
Thread thread3 = new Thread(salesTask1);
thread1.start();
thread2.start();
thread3.start();
}
}
class SalesTask2 implements Runnable {
int tickets = 100;
private Object lock = new Object();
@Override
public void run() {
//假设当前tickets的值是1
// thread3 :当前tickets的值是1, 1>0 ,thread3 进入循环准备卖票,此时发生了线程切换
// thread2 : 当前tickets的值是1, 1>0, thread2进入循环准备卖票,此时发生了线程切换
// thread1 : 当前tickets的值是1, 1>0,thread1 进入循环准备卖票
while (tickets > 0) {
//每次售票的延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当前tickets的值是99
//多卖问题 :前提:tickets--: 1). 读取tickets 变量的值
// 2). 减一 tickets - 1 3) tickets = tickets - 1
//thread2 :拼接字符串, thread2: 售出了第97 若此时发生了线程切换
//thread3 : 拼接字符串: thread3: 售出了第97, 此时发生了线程切换
//thread1 : 拼接字符串窜 :thread1:售出了第97张票
//将以此售票过程,变成一组原子操作
synchronized (lock) {
if (tickets > 0) {//双检查 (double check)
System.out.println(Thread.currentThread().getName() + ":售出了第:" + tickets-- + "张票");
}
}
}
}
}