线程
2563字约9分钟
2025-10-03
线程
一个进程对应的是一个程序
一个进程运行多个任务,这就是线程
单线程:同一时间只能执行一个任务
多线程:同一时间可以执行多个任务
并发:同一时刻,多个任务交替运行(会让人产生同时运行的错觉),就是单核cpu运行多个程序
并行:同一时刻,多个任务同时运行,就是多核cpu运行多个程序
主线程不会堵塞,主线程是和子线程交替执行的
主线程的结束不会影响子线程的调用
public class Thread01 {
public static void main(String[] args) {
cat cat = new cat();
// 启动子线程
// 通过start启动run方法,实现线程
cat.start();
// 如果只是普通的调用run(cat.run()),则不会触发线程机制
System.out.println("主线程:" + Thread.currentThread().getName());
for (int i = 0; i < 60; i++) {
System.out.println("主线程 " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 一个类继承了Thread之后,该类就变成了一个线程
// 重写run代码
class cat extends Thread {
@Override
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println("hi");
try {
// 1000毫秒 = 1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 运行结果
主线程:main
hi
主线程 0
hi
主线程 1
hi
主线程 2
hi
主线程 3
主线程 4
hi
...
Java是单线程机制,所以只能继承一个类
当业务要使用线程,但该业务又正好继承别的类时,我们可以使用 Runnable
接口来实现线程
调用方法如下
public class Thread01 {
public static void main(String[] args) {
cat cat = new cat();
// 创建Thread对象,并且传入业务对象
Thread thread = new Thread(cat);
// 调用start方法
thread.start();
System.out.println("主线程:" + Thread.currentThread().getName());
for (int i = 0; i < 60; i++) {...}
}
}
class Dog { }
class cat extends Dog implements Runnable {
@Override
public void run() {...}
}
线程常用方法
用户线程:线程执行完任务结束或者以通知的方式结束
守护线程:所有用户线程结束,则守护线程结束
线程类名.对应方法名
setName // 设置线程名
getName // 返回线程名
start // 启动线程
run // 启动run方法
setPriority // 更改线程的优先级
getPriority // 获取线程的优先级
sleep // 使当前线程休眠
interrupt // 请求线程停止当前状态(不是强制停止)
yield // 切换线程(是否让出 CPU 由操作系统的线程调度器决定)
join // 切换线程(强制)
setDaemon(true) // 守护线程,当所有线程结束,则守护线程也会跟着结束(即使该线程还没执行完)
public class Thread03 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("zhangsan");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
Thread.sleep(1000 * 5);
// 5秒之后,t停止休眠
t.interrupt();
}
}
class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " 子线程");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
// 当主方法执行了interrupt代码之后,会抛出一个中断异常
// 因此这里可以执行一些业务代码
System.out.println(Thread.currentThread().getName() + " interrupt");
}
}
}
}
// 运行结果
zhangsan 子线程
zhangsan interrupt
zhangsan 子线程
zhangsan 子线程
zhangsan 子线程
zhangsan 子线程
public class Thread04 {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("主线程1\t" + i);
if (i == 2) {
// 当i = 2时,就切换到t1子线程
t1.join();
}
}
}
}
class T1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程2\t" + i);
}
}
}
// 运行结果
子线程2 0
主线程1 0
主线程1 1
子线程2 1
主线程1 2
子线程2 2
子线程2 3
子线程2 4
主线程1 3
主线程1 4
线程同步
synchronized
当多个线程要访问同一个资源的时候,可能会出现数据不同步的问题
synchronized
的作用就是让这些线程进行同步的
- 循环
- 同步代码块
- 判断共享数据是否到了末尾(到了末尾,结束;没到末尾,执行核心代码)
互斥锁
- 非静态方法中的
synchronized (this)
in()
方法是非静态方法,使用synchronized (this)
表示对当前对象实例加锁。- 多个线程操作同一个
H
实例时,会竞争同一把锁;不同实例间的锁互不干扰。
- 静态方法中的
synchronized (H.class)
vi()
是静态方法,使用synchronized (H.class)
表示对H
类的类对象加锁。- 类对象在 JVM 中唯一,因此所有
H
实例的线程调用vi()
时,都会竞争这把类级锁,实现全局同步。
确保多个线程访问同一个资源的时候是一个一个的访问,而不是同时访问
被 static
修饰的方法(静态方法)或属性(静态变量)属于类本身,而不属于类的任何具体对象。
class H implements Runnable {
// 非静态方法同步
public void in() {
synchronized (this) { }
}
// 静态方法同步
public static void vi() {
// 当前类.class
synchronized (H.class) { }
}
}
注意点:推荐使用同步代码块,而不是直接同步整个类或者方法
多个线程的锁对象必须是同一个,也就是说必须是同一个对象调用,不能创建多个对象调用
public class Thread03 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
// 同一个对象锁
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(t);
T t1 = new T();
// 非同一个对象锁
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(t1);
}
}
class T implements Runnable {
@Override
public void run() {
// 同步代码块
synchronized (T.class) { }
}
}
手动锁(lock)
lock获取锁
unlock释放锁
class Money implements Runnable {
// 设置为静态,确保锁的唯一性
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (lood) {
// 获取锁
lock.lock();
try {
System.out.println("java");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
}
}
线程死锁
两个或多个线程相互持有对方所需的资源(锁),且都在等待对方释放资源,导致所有线程永久阻塞的状态
Thread和Runnable的区别
本质上没有区别
但实现Runnable接口的方式更适合多个线程共享一个资源的情况,避免单线程的限制
线程池
提交任务时,线程池会创建新的线程对象,任务完成时,线程会归还线程池,下次在执行任务时就直接调用线程池中的线程,而不用重新创建新线程
public class Xc_Chi {
public static void main(String[] args) {
// 线程池(有上限)
// 设置线程上限为3,所以该线程最多只能存放3个线程
ExecutorService exs = Executors.newFixedThreadPool(3);
exs.submit(new Demo());
exs.submit(new Demo());
exs.submit(new Demo());
exs.submit(new Demo());
exs.submit(new Demo());
exs.submit(new Demo());
exs.submit(new Demo());
// 销毁线程池(线程池中的所有线程会消失)
exs.shutdown();
}
}
class Demo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " ---");
}
}
// 运行结果
// 最多3个线程
pool-1-thread-2 ---
pool-1-thread-1 ---
pool-1-thread-1 ---
pool-1-thread-3 ---
pool-1-thread-1 ---
pool-1-thread-2 ---
pool-1-thread-3 ---
public class Xc_Chi {
public static void main(String[] args) {
// 线程池(无上限)
// int最大值封顶
ExecutorService exs1 = Executors.newCachedThreadPool();
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
exs1.submit(new Demo());
// 销毁线程池(线程池中的所有线程会消失)
exs1.shutdown();
}
}
class Demo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " ---");
}
}
// 运行结果
// 有8个线程
pool-1-thread-2 ---
pool-1-thread-7 ---
pool-1-thread-1 ---
pool-1-thread-4 ---
pool-1-thread-3 ---
pool-1-thread-8 ---
pool-1-thread-6 ---
pool-1-thread-5 ---
自定义线程池
// 自定义线程池
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
3, // 核心线程
6, // 最大线程
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务拒绝策略
);
线程池中线程数量推荐
CPU密集型运算:就是自己电脑的CPU线程数,4核8线程(线程数为8)
根据上图,8线程的CPU,且期望CPU利用率为100%,则线程池中对应的线程数推荐为16
消费者与生产者实例(Lock锁)
消费者
public class Foodie extends Thread {
public void run() {
while (true) {
synchronized (Desk.lock) {
// 判断面条还剩多少
if (Desk.count == 0) {
break;
} else {
// 判断面条是否做好
if (Desk.foodFlag == 0) {
try {
// 线程等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Desk.count--;
System.out.println("消费者正在吃面,还剩 " + Desk.count + "碗");
Desk.lock.notifyAll(); // 唤醒跟这把锁绑定的所有线程
// 改变桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
生产者
public class Cook extends Thread {
public void run() {
while (true) {
synchronized (Desk.lock) {
// 判断面条还有没有
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有面条
if (Desk.foodFlag == 1) {
try {
// 有就等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("厨师做了一碗面");
// 桌子状态:有面
Desk.foodFlag = 1;
// 唤醒等待中的消费者线程
Desk.lock.notifyAll();
}
}
}
}
}
}
中间控制件
public class Desk {
// 0:没面条 1:有面条
public static int foodFlag = 0;
// 面条数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
运行项目
public class ThreadDemo {
public static void main(String[] args) {
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("消费者");
cook.start();
foodie.start();
}
}
运行结果
......
厨师做了一碗面
消费者正在吃面,还剩 2碗
厨师做了一碗面
消费者正在吃面,还剩 1碗
厨师做了一碗面
消费者正在吃面,还剩 0碗
消费者与生产者(堵塞队列)
消费者
public class Foodie extends Thread {
ArrayBlockingQueue<String> s;
public Foodie (ArrayBlockingQueue<String> s) {
this.s = s;
}
public void run() {
while (true) {
// 不断地往队列中获取面条
try {
String string = s.take();
System.out.println(string);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者
public class Cook extends Thread {
ArrayBlockingQueue<String> s;
public Cook (ArrayBlockingQueue<String> s) {
this.s = s;
}
public void run() {
while (true) {
// 不断地往队列中放入面条
try {
s.put("面条");
System.out.println("厨师做了一碗面");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行项目
public class ThreadDemo {
public static void main(String[] args) {
// 堵塞队列对象,设置容量为 1 的有界阻塞队列
// 堵塞队列中有锁,所以不需要额外的加锁
ArrayBlockingQueue<String> s = new ArrayBlockingQueue<>(1);
// 为了数据同步,所以两个对象使用同一个队列
Cook cook = new Cook(s);
Foodie foodie = new Foodie(s);
cook.start();
foodie.start();
}
}