目录
多线程基础
Synchronized
使用多线程模拟售票系统
线程同步机制
具体同步方法-Synchronized
分析同步原理
互斥锁
基本介绍
使用互斥锁来解决售票问题
注意事项和细节
线程的死锁
基本介绍
应用案例
释放锁
下面操作会释放锁
释放锁的分析
下面操作不会释放锁
章节作业
1、编程题
2、编程题
[售票系统],编程模拟三个售票窗口售票,分别使用继承Thread和实现Runnable方式,并分析有什么问题
package com17.ticket;
/**
* @author 甲柒
* @version 1.0
* @title SellTicket
* @package com17.ticket
* @time 2023/3/27 11:41
* 使用多线程,模拟三个窗口同时售票 100张
*/
public class SellTicket {
public static void main(String[] args) {
// //测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里会出现票数超卖现象
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
System.out.println("=====使用Runnable接口方式实现=====");
SellTicket02 sellTicket02 = new SellTicket02();
//这里也会出现票数超卖现象
new Thread(sellTicket02).start();//第1个线程-窗口
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口
}
}
//使用Thread方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
票数会超卖
1、同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码
}
2、synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){
//需要被同步的代码
}
3、如何理解:
就好像某人去厕所前先把门关上(上锁),完事后再出来(解锁),那么其他人就可以在使用厕所了
4、使用synchronized解决售票问题
package com17.syn;
/**
* @author 甲柒
* @version 1.0
* @title SellTicket
* @package com17.syn
* @time 2023/3/29 16:41
* 使用多线程,模拟三个窗口同时售票 100张
*/
public class SellTicket {
public static void main(String[] args) {
// //测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里会出现票数超卖现象
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
// System.out.println("=====使用Runnable接口方式实现=====");
// SellTicket02 sellTicket02 = new SellTicket02();
//
// //这里也会出现票数超卖现象
// new Thread(sellTicket02).start();//第1个线程-窗口
// new Thread(sellTicket02).start();//第2个线程-窗口
// new Thread(sellTicket02).start();//第3个线程-窗口
//测试
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//使用Thread方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
private static int ticketNum = 100;//让多个线程共享ticketNum
private boolean loop = true;//控制run方法变量
public synchronized void sell() {//同步方法,在同一时刻,只能有一个线程来执行run方法
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
loop = false;
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一个同步方法
}
}
}
package com17.syn;
/**
* @author 甲柒
* @version 1.0
* @title SellTicket
* @package com17.syn
* @time 2023/3/29 16:41
* 使用多线程,模拟三个窗口同时售票 100张
*/
public class SellTicket {
public static void main(String[] args) {
// //测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里会出现票数超卖现象
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
// System.out.println("=====使用Runnable接口方式实现=====");
// SellTicket02 sellTicket02 = new SellTicket02();
//
// //这里也会出现票数超卖现象
// new Thread(sellTicket02).start();//第1个线程-窗口
// new Thread(sellTicket02).start();//第2个线程-窗口
// new Thread(sellTicket02).start();//第3个线程-窗口
//测试
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//使用Thread方式
//new SellTicket01.start();
//new SellTicket01.start();
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享ticketNum
// public void m1(){
// synchronized (this){
// System.out.println("hello");
// }
// }
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
}
//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
private static int ticketNum = 100;//让多个线程共享ticketNum
Object object = new Object();
private boolean loop = true;//控制run方法变量
//同步方法(静态的)的锁为当前类本身
//解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中,实现一个同步代码块
/*
synchronized (SellTicket03.class){
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
//说明
//1. public synchronized void sell() {} 就是一种同步方法
//2. 这时锁在 this对象
//3. 也可以在代码块上写 synchronized ,同步代码块,互斥锁还是在this对象
public /*synchronized*/ void sell() {//同步方法,在同一时刻,只能有一个线程来执行run方法
synchronized (/*this*/ object) {
if (ticketNum <= 0) {
System.out.println("售票结束~~~");
loop = false;
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
}
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一个同步方法
}
}
}
多个线程都占用了对方的锁资源,但是不肯相让,导致了死锁,在编程时一定要避免死锁的发生
package com17.syn;
/**
* @author 甲柒
* @version 1.0
* @title DeadLock_
* @package com17.syn
* @time 2023/3/29 17:49
* 模拟线程死锁
*/
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();//保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 True,线程就会先得到 持有 o1 对象锁,然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 False,线程就会先得到 持有 o2 对象锁,然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) {//这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {//这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
代码如下
package com17.exercise;
import java.util.Scanner;
/**
* @author 甲柒
* @version 1.0
* @title Exercise01
* @package com17.exercise
* @time 2023/3/29 18:50
*/
public class Exercise01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);
a.start();
b.start();
}
}
//创建A线程类
class A extends Thread {
private boolean loop = true;
@Override
public void run() {
//输出1-100 数字
while (loop) {
System.out.println((int) (Math.random() * 100 + 1));
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("A线程退出...");
}
public void setLoop(boolean loop) {//可以修改loop变量
this.loop = loop;
}
}
//直到带2个线程从键盘读取了"Q"命令
class B extends Thread {
private A a;
private Scanner scanner = new Scanner(System.in);
public B(A a) {//构造器中,直接传入A类对象
this.a = a;
}
@Override
public void run() {
while (true) {
//接收到用户的输入
System.out.println("请输入你的指令(Q)表示退出:");
char key = scanner.next().toUpperCase().charAt(0);
if (key == 'Q') {
//以通知的方式结束A线程
a.setLoop(false);
System.out.println("b线程退出~~~");
break;
}
}
}
}
代码如下
package com17.exercise;
/**
* @author 甲柒
* @version 1.0
* @title Exercise02
* @package com17.exercise
* @time 2023/3/29 19:14
*/
public class Exercise02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
thread1.setName("t1");
Thread thread2 = new Thread(t);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
//编程收款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2.每次取出1000
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
//解读
//1. 这里使用synchronized实现了线程同步
//2. 当多个线程执行到这里时,就会去争夺this对象锁
//3. 哪个线程争夺到(获取)this对象锁,就执行synchronized代码块,执行完后,会释放this对象锁
//4. 争夺不到this对象锁,就blocked,准备继续争夺
//5.this对象锁是非公平锁
synchronized (this) {//
//判断余额是否够
if (money < 1000) {
System.out.println("余额不足~~~~");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
try {
//休眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}