目录
1、概念
1.1 进程
·1.2 线程
1.3 进程与线程的区别和联系
2、生命周期
2.1 线程的生命周期
2.2 生命周期的状态
3、多线程的实现
3.1 继承 Thread 类
3.2 实现 Runnable 接口
3.3 补充
4、临界资源问题
5、解决临界资源问题
5.1 同步代码段
5.2 同步方法
5.3 手动上锁
6、死锁
7 notify 和 notifyAll
狭义定义:是正在运行的程序的实例。
广义定义:是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。一般由程序,数据集合和进程控制块三部分组成
定义:程序执行中一个单一的顺序控制流程,是程序执行流的最小单元。
①、不同进程之间的 资源不共享
②、线程之间的资源 可以共享
③、线程包含在进程之中的,一个进程至少有一个线程,也可以有多个线程
④、线程是程序执行的最小单位
⑤、进程是重量级的,线程是轻量级。CPU在线程之间的切换要比进程之间的切换块
定义:是线程从初始化完成,到最后的销毁的过程 称之为线程生命周期
新生态: new
线程刚被创建出来,还没进行任何操作。
就绪态: runable
线程已被开启,开始争抢 CPU 的时间片
运行态:run
线程已经抢到 CPU的时间片,开始执行线程中的逻辑代码
堵塞态:Interrupt
一个线程在运行过程中,收到某些操作的影响,放弃已经获取到的CPU时间片。并且不再参与CPU时间片的争夺。此时线程处于挂起
死亡态 :dead
一个线程需要被销毁。
方式一: 继承 Thread 类,重写 run 方法,使用 start 方法开启线程。代码如下:
/**
* 自定义线程类
*/
public class MyThread extends Thread {
/*
* 重写 run 方法
* 将需要并发执行的任务写在 run 方法中
*/
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println("子线程中的 "+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 线程实例化
MyThread thread = new MyThread();
thread.start();
System.out.println("main 线程执行完毕");
}
}
方式二: 通过实现 Runnable 接口,重写 run 方法。将 Runnable 接口的实现类作为参数 传入Thead 类中。
Runnable r = () -> {
for(int i = 0; i< 10; i++){
System.out.println("子线程中执行" + i);
}
};
Thread t = new Thread(r);
t.start();
System.out.println("main 线程执行完毕");
①、继承Thread 类的方式实现多线程比较直观和简单, 但它的弊端是java是单继承的,继承了 Thread 类后就不能继承其他类
②、实现 Runnable 接口方式可以避免单继承问题
③、需要注意的是开启线程的方式是 调用 线程对象的 start 方法,而不是 run 方法
临界资源:被多个线程同时访问的资源(共享资源)
例子:以景区多个售票员同时售票为例
public class ThreadDemo {
/**
* 某景点有 4 个售票员, 同时售票
* @param args
*/
public static void main(String[] args) {
Runnable r = () -> {
while (TicketCenter.restCount > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
}
};
// 4 个线程, 模拟 4 个售票员
Thread t1 = new Thread(r, "thread - 1");
Thread t2 = new Thread(r, "thread - 2");
Thread t3 = new Thread(r, "thread - 3");
Thread t4 = new Thread(r, "thread - 4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketCenter{
// 描述剩余的票的数量
public static int restCount = 100;
}
运行结果:
分析:
有些票被重复卖了, 例如图上的一张票,分别被 线程1 和 线程4 出售。
原因:
线程1进来的时候看到有100张票,此时线程1 没有买票暂时离开了。刚好线程4 进入看到余票也是 100张后,线程4 也没有买票暂时离开; 现在线程1由于之前已经判断了还有余票,所以继续之前的代码,买了1张票剩余99,也暂时离开;线程4进入后因为之前看到100张余票,所以他也买了1张剩余99张
简单理解:
A去买票,此时售票员甲查了一下有100张,但还没直接卖出去时,刚好去上了个厕所。
此时B 也来买票,售票员乙也查了一下有100张, 但也没有直接卖出去的时候,也去上了个厕所。
然后售票员甲回来了,没有再查票的库存,直接登记卖出1张, 剩余99张。
最后售票员乙也回来,也没有查票的库存,直接登记卖了1张,剩余99张。
public class ThreadDemo {
/**
* 某景点有 4 个售票员, 同时售票
*
* @param args
*/
public static void main(String[] args) {
Runnable r = () -> {
while (TicketCenter.restCount > 0) {
synchronized ("") {
// 这里再判断一下, 防止卖出负数票
if (TicketCenter.restCount > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
}
}
}
};
// 4 个线程, 模拟 4 个售票员
Thread t1 = new Thread(r, "thread - 1");
Thread t2 = new Thread(r, "thread - 2");
Thread t3 = new Thread(r, "thread - 3");
Thread t4 = new Thread(r, "thread - 4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketCenter {
// 描述剩余的票的数量
public static int restCount = 100;
}
把同步代码段中的代码抽取处理,做成方法,然后方法上使用 synchronized 关键字修饰
public class ThreadDemo {
private static synchronized void soldTicket() {
// 这里再判断一下, 防止卖出负数票
if (TicketCenter.restCount > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
}
}
/**
* 某景点有 4 个售票员, 同时售票
*
* @param args
*/
public static void main(String[] args) {
Runnable r = () -> {
while (TicketCenter.restCount > 0) {
soldTicket();
}
};
// 4 个线程, 模拟 4 个售票员
Thread t1 = new Thread(r, "thread - 1");
Thread t2 = new Thread(r, "thread - 2");
Thread t3 = new Thread(r, "thread - 3");
Thread t4 = new Thread(r, "thread - 4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketCenter {
// 描述剩余的票的数量
public static int restCount = 100;
}
补充:
同步方法:如果是静态方法,那加的锁就是 类名.class, 如果是非静态方法,那加的锁是对象锁 this
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo {
/**
* 某景点有 4 个售票员, 同时售票
*
* @param args
*/
public static void main(String[] args) {
// 实例化一个锁对象
ReentrantLock lock = new ReentrantLock();
Runnable r = () -> {
while (TicketCenter.restCount > 0) {
// 对临界资源上锁
lock.lock();
// 这里再判断一下, 防止卖出负数票
if (TicketCenter.restCount > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
}
// 对临界资源解锁
lock.unlock();
}
};
// 4 个线程, 模拟 4 个售票员
Thread t1 = new Thread(r, "thread - 1");
Thread t2 = new Thread(r, "thread - 2");
Thread t3 = new Thread(r, "thread - 3");
Thread t4 = new Thread(r, "thread - 4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketCenter {
// 描述剩余的票的数量
public static int restCount = 100;
}
定义:多个线程持有对象的锁对象,而不释放自己锁
public class ThreadDemo {
public static void main(String[] args) {
Runnable r1 = () -> {
synchronized ("A") {
System.out.println("A 线程持有 A锁, 等待B锁");
synchronized ("B") {
System.out.println("A 线程持同时持有 A锁和 B锁");
}
}
};
Runnable r2 = () -> {
synchronized ("B") {
System.out.println("B 线程持有 B锁, 等待A锁");
synchronized ("A") {
System.out.println("B 线程持同时持有 A锁和 B锁");
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
运行结果:
public class ThreadDemo {
public static void main(String[] args) {
// wait : 等待, 是Object 类中的一个方法, 当前的线程释放自己的锁标记, 并且让出CPU 资源, 使得当前线程进入等待队列
// notify: 通知, 是Object 类中的一个方法, 唤醒等待队列中的一个线程, 使这个线程进入锁池
// notifyAll : 通知, 是Object类中的一个方法, 唤醒等待队列中所有的线程, 并这个线程进入锁池
Runnable r1 = () -> {
synchronized ("A") {
System.out.println("A 线程持有 A锁, 等待B锁");
try {
// 释放已经持有的 A锁标记, 进入等待队列
"A".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B") {
System.out.println("A 线程持同时持有 A锁和 B锁");
}
}
};
Runnable r2 = () -> {
synchronized ("B") {
System.out.println("B 线程持有 B锁, 等待A锁");
synchronized ("A") {
System.out.println("B 线程持同时持有 A锁和 B锁");
"A".notifyAll();
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
解决死锁: