多线程
什么是cpu
CPU的中文名称是中央处理器,是进行逻辑运算用的,主要由运算器、控制器、寄存器三部分组成,从字面意思看就是运算就是起着运算的作用,控制器就是负责发出cpu每条指令所需要的信息,寄存器就是保存运算或者指令的一些临时文件,这样可以保证更高的速度。也就是我们的线程运行在cpu之上。CPU
什么是线程和进程
进程是资源分配最小单位,线程是程序执行的最小单位。 计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。总结:进程是资源分配最小单位,线程是程序执行的最小单位什么是进程:cpu从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程一个程序如果被cpu多次被读取到内存中,则变成多个独立的进程什么是线程:线程是程序执行的最小单位,在一个进程中可以有多个不同的线程同时执行。
为什么在进程中存在线程
同一个应用程序中(进程),更好并行处理。
为什么需要使用多线程
采用多线程的形式执行代码,目的就是为了提高程序的效率。目的就是为了提高程序开发的效率比如:现在一个项目只有一个程序员开发,需要开发功能模块会员模块、支付模块、订单模块。
串行和并行(对应电路的串联核并联)
串行也就是单线程执行 代码执行效率非常低,代码从上向下执行;并行就是多个线程并行一起执行,效率比较高。
使用多线程一定提高程序效率
多线程 执行 需要同时执行
不一定,需要了解cpu调度的算法就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,
运行新任务
如果生产环境中开启几百个或者上千个线程,而我们的服务器核数8核16核 32核,这么多线程都会在我们这些cpu上做上下文切换上下文切换:从该线程执行切换到另外的线程 该线程---运行切换为就绪状态。
线程池:和服务器cpu核数 8核 16核
CPU调度时间片
单核的cpu上每次只能够执行一次线程,如果在单核的cpu上开启了多线程,则会发生对每个线程轮流执行 。
Cpu每次单个计算的时间成为一个cpu时间片,实际只有几十毫秒人为感觉好像是在多线程。
对于线程来说,存在等待cpu调度的时候 该线程的状态是为就绪状态,如果被cpu调度则该线程的状态为运行状态
当cpu转让执行其他的线程时,则该线程有变为就绪状态。
如果在单核的cpu之上开启了多线程,底层执行并不是真正意义上的多线程。利用多核多线程性能。
Cpu密集型/IO密集型
Cpu密集型:长时间占用cpu;例如: 视频剪辑
IO密集型 :cpu计算时间短 访问外接设备时间长Input/output
CPU调度算法原理
先来先服务 缺点如果最先来执行的线程 是CPU密集型 这样话可能会一直无法继续执行其他的线程。
最短作业法 谁的计算时间短,谁先来执行。
优先级调度算法 根据重要性将进程分成4个优先级
优先级4 进程D负责画面----
优先级3 进程B和进程C接受用户的指令 重要
优先级2 后台处理器 次要
优先级1
程序计数器
程序计数器是用于存放下一条指令所在单元的地址的地方
寄存器记录该线程上次记录
当线程多余多核cpu的核数,会分配给每个核,那么也就变成单核的形式了
多线程快速入门( 多线程就是实现异步的一个方式。 异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。)
多线程应用场景
客户端(移动App端/)开发;
异步发送短信/发送邮件
将执行比较耗时的代码改用多线程异步执行;
可以提高接口的响应速度异步写入日志 日志框架底层
多线程下载
同步与异步
同步与异步的区别同步概念:就是代码从上向下执行。
异步的概念:单独分支执行 相互之间没有任何影响。
多线程创建方式
1)继承Thread类创建线程
package ThreadSample;
/**
* 继承Thread创建多线程
**/
public class Thread01 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我是子线程");//Thread-0我是子线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"<阻塞完毕>!");
}
public static void main(String[] args) {
//调用run就是普通方法
/**
* 改代码可见的线程为2个
*
*/
System.out.println(Thread.currentThread().getName());//main
//调用start()线程不是立即执行。就绪状态--->等待cpu调度,就绪到运行,主线程走完了子线程也不一定走完
new Thread01().start();//Thread-0
new Thread01().start();//Thread-1
System.out.println("主线程执行完毕!");
}
/**
* 执行结果
* main
* 主线程执行完毕!
* Thread-1我是子线程
* Thread-0我是子线程
*/
}
2)实现Runnable接口创建线程
package ThreadSample;
/**
*实现Runnable(使用较多)
**/
public class Thread02 implements Runnable{
@Override
public void run() {
//设置线程名
Thread.currentThread().setName("蔡徐坤");
System.out.println(Thread.currentThread().getName()+"<我是子线程>!");
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"我是主线程");
//启动新线程,使用匿名内部类
new Thread(new Thread02()).start();
new Thread(()->{System.out.println(Thread.currentThread().getName()+"<我是子线程>!");
}).start();
}
}
5)使用Callable和Future创建线程
Callable和Future 线程可以获取到返回结果 底层基于LockSupport
线程 异步执行 比较耗时间-
从Java 5开始,Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大。call()方法可以有返回值。call()方法可以声明抛出异常。
package ThreadSample;
import java.util.concurrent.Callable;
/**
*
**/
public class ThreadCallable implements Callable<Integer> {
//Callable<Integer>返回值类型为Integer
@Override
public Integer call() throws Exception {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"返回1");
return 1;
}
}
package ThreadSample;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
*
**/
public class ThreadCallable02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 使用该方法
* 会等子线程执行结束后再继续执行主线程,原理,当只想到子线程,主线程进入到阻塞,子线程执行完毕后,主线程被唤醒、
*
*/
System.out.println("主线程开始...");
ThreadCallable threadCallable = new ThreadCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(threadCallable);
new Thread(integerFutureTask).start();
Integer integer = integerFutureTask.get();//获取返回值
System.out.println(Thread.currentThread().getName()+","+integer);
System.out.println("主线程结束...");
}
}
6)使用线程池例如用Executor框架
package ThreadSample;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
**/
public class Test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"我是子线程");
});
}
}
7)spring @Async异步注解 结合线程池
什么是线程安全
当多个线程共享一个全局变量,做写操作时,可能会受到其他线程的干扰,发生线程安全问题
全局变量存放在堆空间
package ThreadSample;
/**
*
**/
public class Test01 implements Runnable{
private int count=100;
@Override
public void run() {
while (true){
if(count>1) {
try{
Thread.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
}
public static void main(String[] args) {
new Thread(new Test01()).start();
new Thread(new Test01()).start();
}
}
两个线程同时争抢全局变量,导致全局变量发生安全问题
多线程如何解决线程安全问题/多线程如何实现同步
核心思想:上锁
再同一个jvm中,多个线程需要竞争锁的资源
在同一个jvm中,多个线程需要竞争锁的资源,最终只能够有一个线程能够获取到锁,多个线程同时抢同一把锁,谁(线程)能够获取到锁,谁就可以执行到该代码,如果没有获取锁成功 中间需要经历锁的升级过程如果一致没有获取到锁则会一直阻塞等待。
如果线程A获取锁成功 但是线程A一直不释放锁线程B一直获取不到锁,则会一直阻塞等待。
代码从那一块上锁?----可能会发生线程安全问题的代码
线程0 线程1 同时获取 this锁,假设线程0 获取到this ,意味着线程1没有获取到锁则会阻塞等待。等我们线程0 执行完count-- 释放锁之后 就会唤醒 线程1从新进入到获取锁的资源。
获取锁与释放锁 全部都是有底层虚拟机实现好了。
package ThreadSample;
/**
*
**/
public class Test01 implements Runnable{
private int count=100;
@Override
public void run() {
while (true){
if(count>1) {
try{
Thread.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
synchronized (this){
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
}
}
public static void main(String[] args) {
Test01 test01 = new Test01();
new Thread(test01).start();
new Thread(test01).start();
}
}
synchronized锁的基本用法
在多线程的情况下 需要是同一个对象锁
Synchronized(对象锁){
需要保证线程安全的代码
}
package ThreadSample;
/**
*
**/
public class Test01 implements Runnable{
private int count=100;
private Object objectLock=new Object();
@Override
public void run() {
while (true){
if(count>1) {
try{
Thread.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
//当一个线程抢到锁后,只有这个线程可以继续执行,其他线程处于阻塞状态
synchronized (objectLock){//可以使用自定义Object锁·
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
// synchronized (this){
// count--;
// System.out.println(Thread.currentThread().getName() + "," + count);
// }
}
}
}
public static void main(String[] args) {
Test01 test01 = new Test01();
new Thread(test01).start();
new Thread(test01).start();
}
}
修饰实例方法和修饰静态方法
package ThreadSample;
/**
*
**/
public class Test01 implements Runnable{
private int count=100;
private Object objectLock=new Object();
@Override
public void run() {
while (true){
cal();
}
}
/**
* 1.synchronized修饰实例方法,就是使用synchronized(this)
* 2.synchronized修饰静态方法,就是使用synchronized(类名.class)
*/
public synchronized void cal(){
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count>1) {
try{
Thread.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
//当一个线程抢到锁后,只有这个线程可以继续执行,其他线程处于阻塞状态
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
Test01 test01 = new Test01();
new Thread(test01).start();
new Thread(test01).start();
}
}
synchronized死锁问题
我们如果在使用synchronized 需要注意 synchronized锁嵌套的问题 避免死锁的问题发生。
案例:
package com.mayikt.thread.days02;
public class DeadlockThread implements Runnable {
private int count = 1;
private String lock = "lock";
@Override
public void run() {
while (true) {
count++;
// 1%2= 2%2= 3%2= 4%2
if (count % 2 == 0) {
synchronized (lock) {
a();
}
} else {
synchronized (this) {
b();
}
}
}
}
public synchronized void a() {
System.out.println(Thread.currentThread().getName() + ",a方法...");
}
public void b() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ",b方法...");
}
}
public static void main(String[] args) {
DeadlockThread deadlockThread = new DeadlockThread();
Thread thread1 = new Thread(deadlockThread);
Thread thread2 = new Thread(deadlockThread);
thread1.start();
thread2.start();
}
}
线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁
线程2 先获取this锁, 进入到b方法需要自定义对象的lock锁
线程1 线程2 是在同时执行
线程1 | 线程2 |
---|---|
先获取到自定义对象的lock锁 | 先获取this锁 |
需要线程2已经持有的this锁 | 线程1已经持有自定义对象的lock锁 |
死锁检测工具:D:\path\jdk\jdk8\bin\jconsole.exe
springmvc接口使用
需要注意:Spring MVC Controller默认是单例的 需要注意线程安全问题
单例的原因有二:
1、为了性能。
2、不需要多例。
@Scope(value = "prototype") 设置为多例
多线程之间的通讯
wait()和notify()使用注意事项
package ThreadSample;
/**
*
**/
public class Test01 {
public static void main(String[] args) throws InterruptedException {
new Test01().cal();
}
public void cal() throws InterruptedException {
/**
* this,wait(),this.notify();必须配合synchronized ()使用
*/
// this.notify();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread().getName()+"进入阻塞状态---");
//执行该方法,线程进行阻塞,需要唤醒才能继续执行
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
synchronized (this){//里面的对象必须统一
Thread.sleep(3000);
this.notify();
System.out.println(Thread.currentThread().getName()+"唤醒阻塞线程");
}
}
}
wait、notify生产者和消费者
package ThreadSample;
/**
*
**/
public class Thread04 {
class Res{
public String username;
public char sex;
public boolean flag=false;
}
/**
* 输入线程
*/
class InputThread extends Thread{
private Res res;
public InputThread(Res res){
this.res=res;
}
@Override
public void run() {
int count=0;
while (true){
synchronized (res) {
if(res.flag){
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count == 0) {
res.username = "于圣军";
res.sex = '男';
} else {
res.username = "小微";
res.sex = '女';
}
res.flag=true;
//唤醒输出线程
res.notify();
}
count=(count+1)%2;
}
}
}
/**
* 输出线程
*/
class OutputThread extends Thread{
private Res res;
public OutputThread(Res res){
this.res=res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
if(!res.flag){
//如果res.flag为false,res主动释放锁
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(res.username + "," + res.sex);
res.flag=false;
res.notify();
}
}
}
}
public static void main(String[] args) {
new Thread04().print();
}
public void print(){
//全局对象
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutputThread outputThread = new OutputThread(res);
inputThread.start();
outputThread.start();
}
}
//notify后不会马上又拿到锁,需要和其他之前没有拿到的进行竞争
join
join使用
按顺序执行
t1,t2,t3三个线程
按顺序执行,需要再t2run中写t1.join(),t3run中t2.join()
多线程7种状态
new Thread();创建线程
start();就绪状态
cpu调到;运行状态
执行完毕:死亡状态
线程没有获取到锁(升级到重量级锁)阻塞等待
持有锁的线程,释放锁的时候,会唤醒正在阻塞的线程(没有获取到锁的先后曾)
拿到锁到运行状态
调用wait(1000)或join(1000)/sleep(1000):超时等待状态,时间到了之后进入到就绪状态
wait/join:等待状态,被唤醒后进入就绪状态
sleep防止cpu100%
用户线程和用户线程
java分为两种线程:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程
通过Thread.setDaemon(true)设置为守护线程。如果不设置次属性,默认为用户线程
-
守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程
-
用户线程是独立存在的,不会因为其他用户线程退出而退出
-
package ThreadSample; /** * **/ public class Test05 { public static void main(String[] args) { /** * 将子线程设置为守护线程,main为用户线程,当用户线程结束,守护线程不管有没有执行完都会退出 */ Thread thread = new Thread(new Runnable() { @Override public void run() { while (true) { } } }); thread.setDaemon(true); thread.start(); System.out.println("主线程结束"); } }
如何安全停止一个线程
中断阻塞线程
thread.interrupt();
非阻塞状态无法中断
需要自己判断
Lock
比之前的sy更加灵活,基于AQS实现
package ThreadSample;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
**/
public class Thread05 {
private Lock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread05 thread05 = new Thread05();
thread05.print();
Thread.sleep(500);
thread05.print2();
}
public void print(){
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();//锁
System.out.println("获取锁成功-1");
lock.unlock();//释放锁
}
}).start();
}
public void print2(){
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("获取锁成功-2");
}
}).start();
}
}
Con
package ThreadSample;
import duotai.Animal;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
**/
public class Test06 {
private Lock lock=new ReentrantLock();
private Condition condition;
public static void main(String[] args) throws InterruptedException {
Test06 test06 = new Test06();
Thread.sleep(3000);
test06.sig();
}
public void sig(){
condition=lock.newCondition();
lock.lock();
condition.signalAll();
lock.unlock();
}
public void cat(){
condition=lock.newCondition();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
yield
主动释放执行权
package ThreadSample;
/**
*
**/
public class Thread12 extends Thread{
public Thread12(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(i==30) {
System.out.println(Thread.currentThread().getName() + ",释放cpu执行权");
this.yield();
}
System.out.println(Thread.currentThread().getName()+","+i);
}
}
public static void main(String[] args) {
new Thread12("t1").start();
new Thread12("t2").start();
}
}
多线程的优先级
默认范围1-10
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义