程序 进程 线程
程序
一段代码 一个蓝本 一个模子
进程
正在运行的程序 程序运行起来就会创建一个进程
进程特点: 动态性
并发性
独立性
线程
CPU 调度的最小单位,进程内部的执行单元(进程由线程组成)
线程、进程关系
- 进程包含线程,一个进程中可以包含多个线程,可以有单线程、多线程
- 线程必须在进程中,线程离不开进程
线程特点
- 轻量级进程
- 独立调度的基本单位
- 可并发执行
- 共享进程资源
线程、进程区别
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开 销 | 每个进程都有独立的代码和数据空间(进程上线文),进程间切换开销大 | 线程可以看成轻量级进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程间切换开销小 |
所处环境 | 同一操作系统中能同时运行多个任务(程序) | 同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行时会为每个进程分配不同的内存区域 | 除了CPU外不会为线程分配内存(线程所使用的资源是所属的进程资源),线程组只能共享资源 |
包含关系 | 一个进程内拥有多个线程,执行过程不是一条线,而是由多条线(线程)共同完成 | 线程是进程的一部分 |
线程 生命周期
线程是一个动态执行的过程,也有一个从产生到死亡的过程
新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态
它保持这个状态直到程序 start() 这个线程
就绪状态
当线程对象调用了 start() 方法之后,该线程就进入就绪状态
就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度
运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态,在睡眠时间已到或获得设备资源后可以重新进入就绪状态
阻塞状态可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态
死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
多线程
继承 Thread
示例
继承 Thread 类,重写 run() 方法
@Slf4j
class MyThread extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
// String threadName = this.getName(); // 获取线程名称 仅继承可用 this: 当前线程
String threadName = Thread.currentThread().getName(); // 获取线程名称
log.info("Thread >>> {}", threadName);
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 开启线程
}
}
线程名称
`方式一` Thread.setName()
@Slf4j
class MyThread extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
String threadName = Thread.currentThread().getName(); // 获取线程名称
log.info("Thread >>> {}", threadName);
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("LOvOT"); // 设置线程名称
thread.start(); // 开启线程
}
}
`方式二` 构造器
@Slf4j
class MyThread extends Thread {
// 构造器 设置线程名称
public MyThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
log.info("Thread >>> {}", threadName);
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread("LOvOT"); // 设置线程名称
thread.start(); // 开启线程
}
}
实现 Runnable
实现 Runnable 接口,重写 run() 方法,Runnable 实现类对象作为 Thread 对象的 target 创建并开启线程
@Slf4j
class MyRunnable implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
log.info("Runnable >>> {}", threadName);
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable, "LOvOT"); // 创建 Thread 实例 同时设置线程名称
thread.start(); // 开启线程
}
}
实现 Callable + Future
实现 Callable 接口,重写 call() 方法,并且有返回值
创建 Callable 实现类的实例,使用 FutureTask 类包装 Callable 对象,其中 FutureTask 对象封装了 Callable 对象中 call() 方法的返回值
FutureTask 对象作为 Thread 对象的 target 创建并开启线程,FutureTask 对象通过 get() 方法获取子线程执行结束后的返回值
@Slf4j
class MyCallable implements Callable<String> {
@Override
public String call() {
log.info("Implements Callable >>> {}", Thread.currentThread().getName());
return "success";
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
MyCallable callable = new MyCallable(); // Callable 实现类实例
FutureTask<String> futureTask = new FutureTask<>(callable); // FutureTask 类实例 包装 Callable 对象
Thread thread = new Thread(futureTask, "LOvOT"); // 创建 Thread 实例 同时设置线程名称
thread.start(); // 开启线程
try {
// 获取返回值
String result = futureTask.get();
log.info("Callable Result >>> {}", result); // success
} catch (Exception e) {
e.printStackTrace();
}
}
}
案例
龟兔赛跑
模拟线程间资源竞争
@Slf4j
class WuGuiThread extends Thread {
// 构造器 设置线程名称
public WuGuiThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = this.getName(); // 获取线程名称
while (true) {
log.info("WuGui >>> {}", threadName);
}
}
}
@Slf4j
class TuZiThread extends Thread {
@Override
public void run() {
String threadName = this.getName(); // 获取线程名称
while (true) {
log.info("TuZi >>> {}", threadName);
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
WuGuiThread wuGuiThread = new WuGuiThread("乌龟");
TuZiThread tuZiThread = new TuZiThread();
tuZiThread.setName("兔子"); // 设置线程名称
wuGuiThread.start();
tuZiThread.start();
}
}
火车票
实现10张火车票同时被4个窗口抢售
尝试一
继承 Thread 类,开启四个线程,共同抢售
@Slf4j
class TicketThread extends Thread {
private int ticketNum = 10; // 总票数
// 构造器 设置线程名称
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = this.getName(); // 获取线程名称
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
log.info("Ticket >>> {} >>> 票:{}", threadName, ticketNum--);
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new TicketThread("窗口" + i).start();
}
}
}
[!note]
火车票被重复抢售
四个窗口均售卖出10张
尝试二
static 修饰符 修饰总票数 总票数被所有对象共享
@Slf4j
class TicketThread extends Thread {
private static int ticketNum = 10; // 总票数 static 修饰
// 构造器 设置线程名称
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = this.getName(); // 获取线程名称
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
log.info("Ticket >>> {} >>> 票:{}", threadName, ticketNum--);
// 线程休眠 50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new TicketThread("窗口" + i).start();
}
}
}
[!note]
共享资源
需使用静态成员变量仍旧存在车票被重复抢购情况
尝试三
创建 Runnable 实现类,实例化一个实现类对象,开启四个线程,共同抢售
@Slf4j
class TicketRunnable implements Runnable {
private int ticketNum = 10; // 总票数
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
log.info("Ticket >>> {} >>> 票:{}", threadName, ticketNum--);
// 线程休眠 50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
for (int i = 0; i < 4; i++) {
new Thread(ticketRunnable, "窗口" + i).start();
}
}
}
[!note]
实现类中总票数无需使用静态修饰符修饰
仍旧存在车票被重复抢购情况
尝试四
同步代码块 synchronized
用于解决线程安全问题,保证某一时刻只允许一个线程进行运行
@Slf4j
class TicketRunnable implements Runnable {
private int ticketNum = 10; // 总票数
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
synchronized (this) {
log.info("Ticket >>> {} >>> 票:{}", threadName, ticketNum--);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
for (int i = 0; i < 4; i++) {
new Thread(ticketRunnable, "窗口" + i).start();
}
}
}
[!note]
同步代码块能保证同一时间只有一个窗口售票,但是不能解决出售不存在的票
因为循环条件被多个线程进行判断,锁释放后满足循环条件线程都能进来
尝试五(终版)
二次判断 同步代码块中再次判断总票数
synchronized 代码块
@Slf4j
class TicketRunnable implements Runnable {
private int ticketNum = 10; // 总票数
@Override
public void run() {
String threadName = Thread.currentThread().getName(); // 获取线程名称
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
synchronized (this) {
if (ticketNum > 0) { // 二次判断 解决最后一张票问题
log.info("Ticket >>> {} >>> 票:{}", threadName, ticketNum--);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
for (int i = 0; i < 4; i++) {
new Thread(ticketRunnable, "窗口" + i).start();
}
}
}
synchronized 方法
@Slf4j
class TicketRunnable implements Runnable {
private int ticketNum = 10; // 总票数
@Override
public void run() {
while (ticketNum > 0) { // 有余票 所有窗口均可抢售
saleTicket();
}
}
private synchronized void saleTicket() {
if (ticketNum > 0) { // 二次判断 解决最后一张票问题
log.info("Ticket >>> {} >>> 票:{}", Thread.currentThread().getName(), ticketNum--);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
for (int i = 0; i < 4; i++) {
new Thread(ticketRunnable, "窗口" + i).start();
}
}
}
银行提现
两人同时提现,不足100元时提示
Account
class Account {
private int money = 510;
public int getMoney() {
return money;
}
// 提现
public void cashMoney() {
money -= 100;
}
}
AccountTest
@Slf4j
public class AccountTest {
public static void main(String[] args) {
AccountRunnable accountRunnable = new AccountRunnable();
for (int i = 0; i < 4; i++) {
new Thread(accountRunnable, "人员:" + i).start();
}
}
}
synchronized this
@Slf4j
class AccountRunnable implements Runnable {
// 共享账户
Account account = new Account();
@Override
public void run() {
while (account.getMoney() >= 100) { // 循环判断 持续提现
synchronized (this) {
if (account.getMoney() > 100) { // 二次判断账户余额
account.cashMoney(); // 账户提现
log.info("Account 提现 >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.info("Account 余额不足 >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
}
}
}
}
}
synchronized 方法
@Slf4j
class AccountRunnable implements Runnable {
// 共享账户
Account account = new Account();
@Override
public void run() {
while (account.getMoney() > 100) { // 循环判断 持续提现
_cashAccount();
}
}
private synchronized void _cashAccount() {
if (account.getMoney() >= 100) { // 二次判断账户余额
account.cashMoney(); // 账户提现
log.info("Account >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.info("Account 余额不足 >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
}
}
}
synchronized obj
@Slf4j
class AccountRunnable implements Runnable {
// 共享账户
Account account = new Account();
@Override
public void run() {
while (account.getMoney() > 100) { // 循环判断 持续提现
synchronized (account) {
if (account.getMoney() >= 100) { // 二次判断账户余额
account.cashMoney(); // 账户提现
log.info("Account >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.info("Account 余额不足 >>> {} >>> 余额:{}", Thread.currentThread().getName(),
account.getMoney());
}
}
}
}
}
[!note]
共享账户
同步代码块 指定对象 synchronized(account)
生产消费商品
循环生产不同商品,生产者生产一个商品,消费者消费一个商品,仓库只能存在一个商品
package com.lovot.test;
import lombok.extern.slf4j.Slf4j;
class Goods {
private String name; // 商品名称
public static final int Num = 10; // 商品数量
private boolean flag;// 标记仓库有无商品
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
@Slf4j
class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for (int i = 0; i < Goods.Num; i++) {
log.info("{} >>> 循环 >>> {}", Thread.currentThread().getName(), i);
synchronized (goods) { // 共享商品仓库 加锁
log.info("{} >>> 占锁", Thread.currentThread().getName());
// 仓库有商品 生产者等待
if (goods.isFlag()) {
try {
log.info("{} >>> 生产者等待", Thread.currentThread().getName());
goods.wait();// 生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 仓库无商品 生产者生产
if (i % 2 == 0) { // 生产康师傅牛肉面
goods.setName("康师傅牛肉面");
} else { // 生产哇哈哈AD钙奶
goods.setName("哇哈哈AD钙奶");
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
goods.setFlag(true);
log.info("{} >>> 商品:{}", Thread.currentThread().getName(), goods.getName());
goods.notify(); // 唤醒消费者 进行消费
}
}
}
}
@Slf4j
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
for (int i = 0; i < Goods.Num; i++) {
log.info("{} >>> 循环 >>> {}", Thread.currentThread().getName(), i);
synchronized (goods) { // 共享商品仓库 加锁
log.info("{} >>> 占锁", Thread.currentThread().getName());
// 仓库无商品 消费者等待
if (!goods.isFlag()) {
try {
log.info("{} >>> 消费者等待", Thread.currentThread().getName());
goods.wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("{} >>> 商品:{}", Thread.currentThread().getName(), goods.getName());
goods.setFlag(false); // 商品已消费 重置标识
goods.notify(); // 唤醒生产者 继续生产
}
}
}
}
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
Goods goods = new Goods(); // 商品
Producer producer = new Producer(goods); // 生产者
Consumer consumer = new Consumer(goods); // 消费者
new Thread(producer, "生产者").start();
new Thread(consumer, "消费者").start();
}
}