Java多线程
线程的基本概念
进程:
每个进程都有独立的代码和数据空间(进程上下文),进程间的切
换会有较大的开销,一个进程包含 1–n 个线程。
b) 线程:
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程
序计数器(PC),线程切换开销小。
) 并行与并发
i. 并行:多个 cpu 实例或者多台机器同时执行一段处理逻辑,是真正
的同时。
ii. 并发:通过 cpu 调度算法,让用户看上去同时执行,实际上从 cpu
操作层面不是真正的同时。并发往往在场景中有公用的资源,那么
针对这个公用的资源往往产生瓶颈,我们会用 TPS 或者 QPS 来反应
这个系统的处理能力。
线程的生命周期
新建状态:
i. 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程
对象就处于新建状态。它保持这个状态直到程序 start()这个线程。
b) 就绪状态:
i. 当线程对象调用了 start()方法之后,该线程就进入就绪状态,该状态
的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。
c) 运行状态:
i. 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便
处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状
态、就绪状态和死亡状态。
d) 阻塞状态:
i. 如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去
所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间
已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait()方法,使线程进入到等
待阻塞状态。 - 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别
的线程占用,则 JVM 会把该线程放入锁池中。 - 其他阻塞:通过调用线程的 sleep()或 join()发出了 I/O 请求时,
线程就会进入到阻塞状态。当 sleep()状态超时,join()等待线程
终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
e) 死亡状态:
i. 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就
关为跟你一起学多线程
切换到终止状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmwG8nOz-1658798549782)(C:\Users\2537376648\AppData\Roaming\Typora\typora-user-images\image-20220416110128231.png)]
线程的实现方式
通过继承 Thread 类
package com.dailyblue.java.pack0108;
// 线程的第一种实现方式
// 1⃣️ 继承Thread类
public class DemoA extends Thread {
// 2⃣️ 重新run方法 当run放被执行时 线程进入运行状态
public void run() {
for (int i = 0; i < 100; i++) {
// 获取到当前运行的线程名
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ":" + i);
}
// run方法全部执行完毕后线程进入死亡状态 释放当前线程对象
}
/*
1.为什么书写的是run方法,但是调用的却是start?
2.为什么调用的是start方法,但是执行的是run方法?
3.为什么a1的run未执行完毕,a2的run方法就去执行了?
*/
public static void main(String[] args) {
// 当产生一个线程类型(Thread类或者它的子类)的对象时 进入新建状态
DemoA a1 = new DemoA(); // 产生了一个对象
DemoA a2 = new DemoA();
// 当线程对象调用了start方法时 进入就绪状态
// 调用了a1的start方法
a1.start();// 我准备好,只欠东风 只等待CPU执行
a2.start();
}
}
/*
线程的生命周期
新建、就绪、运行、阻塞和死亡。
*/
b) 通过实现 Runnable 接口
package com.dailyblue.java.pack0108;
// 线程的第二种实现方式
// 1⃣️ 实现一个Runnable接口
public class DemoB implements Runnable {
// 2⃣️ 重写run方法
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
// 这个时候不是新建状态 并不是一个线程对象
DemoB b1 = new DemoB();
DemoB b2 = new DemoB();
// 产生2个线程去包装这两个任务b1,b2
// 新建状态
Thread t1 = new Thread(b1);
Thread t2 = new Thread(b2);
t1.start();
t2.start();
}
}
c) 通过 Callable 和 Future 创建线程
i. 创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为
线程执行体,并且有返回值(类似于 run()方法)。
ii. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,
该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
iii. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
iv. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
package com.dailyblue.java.pack0108;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 线程的第三种实现方式
// 1⃣️ 实现一个Callable接口
public class DemoC implements Callable {
// 2⃣️重写call方法 这个方法能够得到返回结果 其他跟run一致
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
return "1-20之和:" + sum;
}
public static void main(String[] args) throws Exception{
DemoC c1 = new DemoC();
DemoC c2 = new DemoC();
FutureTask ft1 = new FutureTask(c1);
FutureTask ft2 = new FutureTask(c2);
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
t1.start();
t2.start();
// 获取到返回结果
Object result1 = ft1.get();
Object result2 = ft2.get();
System.out.println(result1);
System.out.println(result2);
}
}
/*
1.多线程常见概念
2.线程的生命周期
3.线程的三种实现方式
*/
线程的安全和同步
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
package com.dailyblue.java.pack0108;
// 演示了线程安全问题----不安全的情况---如果通过上锁的方式去实现了线程安全
public class DemoE {
public static void main(String[] args) {
Counter counter = new Counter();
for (int i = 0; i < 4; i++) {
DemoE1 e1 = new DemoE1(counter);
Thread t = new Thread(e1);
t.start();
}
// 暂停3s之后执行后续代码
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int result = counter.getCount();
System.out.println(result);
}
}
// 实现了一个线程任务
class DemoE1 implements Runnable {
// 声明了一个counter属性 然后通过构造器给属性赋值
private Counter counter;
public DemoE1(Counter counter){
this.counter = counter;
}
public void run() {
// 循环1000执行addCount() 实现对count的自增
for (int i = 0; i < 1000; i++) {
counter.addCount(); // 自增
}
}
}
// 是一个计数器类
class Counter {
private int count = 0; // 计数器
// 调用一次这个方法
// 需要在某一个线程执行这个方法时 其他线程不能执行 可以通过🔒来实现 synchronized:上锁
public synchronized void addCount() {
count++; // 自增
}
public int getCount() {
return count;
}
}
/*
0-1-2-3-4-5-6-7-8
a1 3-->4
a2 3-->4
*/
synchronized的用法
package com.dailyblue.java.pack0108;
// synchronized关键字的使用场景 同步锁
public class DemoF {
private int count = 10;
// 对当前方法上锁的方式 保证线程安全 场景一
public synchronized void methodA() {
System.out.println(Thread.currentThread().getName()+"开始执行递减操作....");
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "执行了相减操作,目前值:" + (count--));
return;
}
System.out.println("count值必须大于等于0!");
}
private Object obj = new Object();
public void methodB() {
System.out.println(Thread.currentThread().getName()+"开始执行递减操作....");
// 对一段代码块进行上锁操作,代码块中需要填写Object类的一个对象 场景二
synchronized (obj){
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "执行了相减操作,目前值:" + (count--));
return;
}
}
System.out.println("count值必须大于等于0!");
}
public static void main(String[] args) {
DemoF f = new DemoF();
for (int i = 0; i < 4; i++) {
DemoF1 f1 = new DemoF1(f);
Thread t1 = new Thread(f1);
t1.start();
}
}
}
class DemoF1 implements Runnable {
private DemoF f;
public DemoF1(DemoF f) {
this.f = f;
}
public void run() {
for (int i = 0; i < 10; i++) {
f.methodA();
}
}
}
ThreadLocal的用法
package com.