目录
🧈4.死锁
1.相关概念
1.1程序
为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
1.2进程
- 程序的一次执行过程,或是正在内存中运行的应用程序。
- 每个进程都有一个独立的内存空间。
- 程序是静态的,进程是动态的。
- 进程是操作系统调度和分配资源的最小单位。
1.3线程
- 进程可进一步细化为线程,是程序内部的一条执行路径。
- 一个进程中至少有一个线程。
- 一个进程同一时间若 并行 执行多个线程,就是支持多线程的。
- 线程作为 CPU调度和执行的最小单位。
1.4并行
- 指两个或多个事件在 同一时刻 发生(同时发生)。
- 指在同一时刻,有多条指令 在多个CPU 上 同时 执行。
比如:多个人同时做不同的事。
1.5并发
- 指两个或多个事件在同一个时间段内发生。
- 即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上目有名个进程同时执行的效果
2.创建和启动线程
Java语言的JvM允许程序运行多个线程,使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
2.1继承Thread类
- 1.创建一个类,继承Thread
- 2.重写Thread类的run()方法,将此线程要执行的操作,声明在此方法中
- 3.创建当前Thread的子类的对象
- 4.通过对象调用start(), start():1.启动线程,2.调用当前线程的run()方法
public class MyTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber p1 = new PrintNumber();
//4.调用start()方法
p1.start();
//main()所在线程执行的操作
for (int i=0;i<=100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
//1.创建子类,继承Thread
class PrintNumber extends Thread {
//2.重写run()方法
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
- 不能使用p1.run()替换p1.start()的调用
- 不能让已经执行start()的线程,再次执行start(),否则报错,应重新创建对象再调用start()
2.2实现Runnable接口
- 1.创建一个类,实现Runnable接口
- 2.实现接口中的run()方法,将此线程要执行的操作,写在run()方法中
- 3.创建当前实现类的对象
- 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
- 5.Thread类的实例调用start()
public class RunnableTest {
public static void main(String[] args) {
//3.创建实现类的对象
Number n1 = new Number();
//4.将对象传递到Thread的构造器中
Thread thread = new Thread(n1);
//5.调用start()方法
thread.start();
}
}
//1.创建类,实现Runnable接口
class Number implements Runnable {
//2.实现run()方法,并将要执行的操作,写在run()方法中
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() +"-偶数:"+i);
}
}
}
}
也可以使用匿名实现RUnnable接口,
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "-偶数:" + i);
}
}
}
}).start();
使用Runnable的好处:
- 1.实现的方式,避免类的单继承的局限性
- 2.更适合处理共享数据的问题
- 3.实现了代码和数据的分类
2.3常用方法
- 1.start():1.启动线程 2.调用线程的run()方法
- 2.run():将线程要执行的操作,写在run()方法中
- 3.currentThread():获取当前代码执行所对应的线程
- 4.sleep():静态方法,睡眠指定毫秒数
- 5.yield():静态方法,释放cpu的执行权
- 6.jion():在线程A中通过线程B调用jion(),线程A进入阻塞状态直到线程B执行完成。
- 7.isAlive():判断当前线程是否存活
- 2.4.线程的生命周期
3.线程安全
3.1问题
模拟三个窗口卖票,在第一个线程未结束的同时,其他线程参与进来,会出现重复票和错票
3.2解决
必须保证一个线程a在操作共享数据的过程中,其他线必须等待,直到线程a操作结束,其他线程才可以继续操作
3.3线程的同步机制
- 方式一:同步代码块
synchronized (同步监视器){
操作共享数据的代码
}
- 共享数据:多个线程需要操作的数据。
- 同步监视器:俗称 锁 。哪个线程获取了锁,哪个线程就能执行代码块。
- 同步监视器可以使用任何一个类的对象充当,当多个线程必须共用同一个同步监视器。
public class WindowTest {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
Thread t1 = new Thread(saleTicket);
Thread t2 = new Thread(saleTicket);
Thread t3 = new Thread(saleTicket);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SaleTicket implements Runnable {
int ticket = 100;
Object object = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号:" + ticket);
ticket--;
} else {
System.out.println(Thread.currentThread().getName()+"票数不足!");
break;
}
}
}
}
}
- 方式二:同步方法
public class WindowTest {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
Thread t1 = new Thread(saleTicket);
Thread t2 = new Thread(saleTicket);
Thread t3 = new Thread(saleTicket);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SaleTicket implements Runnable {
static int ticket = 100;
static Object object = new Object();
Boolean isFlag=true;
@Override
public void run() {
while (isFlag) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
show();
}
}
public synchronized void show(){ //此时的同步监视器就是this,即代码中的saleTicket,唯一的
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号:" + ticket);
ticket--;
}else {
System.out.println(Thread.currentThread().getName()+"票数不足");
isFlag=false;
}
}
}
注:
非静态的同步方法,默认同步监视器是this
静态的同步方法,默认同步监视器是当前类本身
synchronized
好处:解决了线程安全问题
弊端:在操作共享数据时,多线程其实是串行执行的,性能较低
4.死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
4.1原因
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待
4.2解决
- 针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
- 针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
- 针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
- 针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
5.线程通信的方法
- wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
- notify():一但执行此方法,就会唤醒wait()的线程中优先级最高的那个线程,如果被wait()唤醒的线程优先级相同,则随即唤醒一个
- notifyAll():一但执行此方法,就唤醒所有被wait的线程
public class AdTest {
public static void main(String[] args) {
PrintNumbers printNumbers = new PrintNumbers();
Thread thread1 = new Thread(printNumbers, "线程1");
Thread thread2 = new Thread(printNumbers, "线程2");
thread1.start();
thread2.start();
}
}
class PrintNumbers implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();//线程执行此方法,进入等待状态,同时释放同步监视器的调用
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
wait() VS sleep()
相同点:一旦执行,当前线程都会阻塞
不同点:声明的位置、使用场景、sleep不会释放同步监视器、阻塞的方式