2025年java多线程(详)

java多线程(详)目录 一 什么叫线程 那我们要先了解什么叫进程 线程依赖于进程而存在的 二 多线程的创建 方式一 继承 Thread 类 方式二 实现 Runnable 接口 方式三 JDK 5 0 新增 实现 Callable 接口 三种方式的比较 三 线程 Thread 的常用方法 四

大家好,我是讯享网,很高兴认识大家。

目录

一,什么叫线程?        那我们要先了解什么叫进程,线程依赖于进程而存在的。

二.多线程的创建

方式一:继承Thread类

方式二:实现Runnable接口

方式三:JDK 5.0新增:实现Callable接口

三种方式的比较

三.线程Thread的常用方法

四.线程调度 

五.线程控制

六.线程的生命周期:

七.线程同步

1.同步代码块:

2.同步方法:

3.lock锁

 八.线程池

1.概念

2.不使用线程池的问题    

3.工作原理

4.如何得到线程池对象

 5.ThreadPoolExecutor构造器的参数说明

6.线程池常见面试题:

作者有话说 


一,什么叫线程?
        那我们要先了解什么叫进程,线程依赖于进程而存在的。

进程:正在运行的程序

  • 是系统进行资源调用和资源分配的独立单位
  • 每一个进程都有它自己的内存空间和系统资源

线程:是进程中的单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径就是单线程程序

记事本程序:在调出页面设置的时候只能在关闭页面设置之后进行其他操作,否则无法进行其他操作。

  • 多线程:一个进程如果只有多条执行路径就是多线程程序

扫雷程序:点击第一下时间开始计时,时间计时的同时可以玩扫雷游戏

二.多线程的创建

方式一:继承Thread类

  • Java是通过java.lang.Thread 类来代表线程的。(不需要导包)
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式。

方法:

  1. 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

为啥要重写run()方法:

        因为在MyThread里面还有其他的代码,并不是所有的代码都需要被线程执行,为了区分哪些被线程执行,java就提供了一个run()方法

  1. 创建MyThread类的对象
  2. 调用线程对象的start()方法启动线程(启动后还是执行run方法的)

优缺点:

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。

/ 目标:多线程的创建方式一:继承Thread类实现。 */ public class ThreadDemo1 { public static void main(String[] args) { // 3、new一个新线程对象 Thread t = new MyThread(); // 4、调用start方法启动线程(执行的还是run方法) //run方法就是一个普通的方法,没有真正启动一个线程,就会把run方法执行完毕,才向下执行, //就是会先执行run方法 只有当run执行完毕才会执行其他线程 t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出:" + i); } } } / 1、定义一个线程类继承Thread类 */ class MyThread extends Thread{ / 2、重写run方法,里面是定义线程以后要干啥 */ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程执行输出:" + i); } } }

讯享网

         直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。 只有调用start方法才是启动一个新的线程执行。

方式二:实现Runnable接口

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理。
  4. 调用线程对象的start()方法启动线程

构造器

说明

public Thread(String name)

可以为当前线程指定名称

public Thread(Runnable target)

封装Runnable对象成为线程对象

public Thread(Runnable target ,String name )

封装Runnable对象成为线程对象,并指定线程名称

优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。 

讯享网/ 目标:学会线程的创建方式二,理解它的优缺点。 */ public class ThreadDemo2 { public static void main(String[] args) { // 3、创建一个任务对象 Runnable target = new MyRunnable(); // 4、把任务对象交给Thread处理 Thread t = new Thread(target); // Thread t = new Thread(target, "1号"); // 5、启动线程 t.start(); for (int i = 0; i < 10; i++) { System.out.println("主线程执行输出:" + i); } } } / 1、定义一个线程任务类 实现Runnable接口 */ class MyRunnable implements Runnable { / 2、重写run方法,定义线程的执行任务的 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子线程执行输出:" + i); } } } 

方式三:JDK 5.0新增:实现Callable接口

1、前2种线程创建方式都存在一个问题:

         他们重写的run方法均不能直接返回结果。 不适合需要返回线程执行结果的业务场景。

2、怎么解决这个问题呢?

         JDK 5.0提供了Callable和FutureTask来实现。 这种方式的优点是:可以得到线程执行的结果。

3.多线程的实现方案三:利用Callable、FutureTask接口实现。

(1)、得到任务对象

         定义类实现Callable接口,重写call方法,封装要做的事情。

        用FutureTask把Callable对象封装成线程任务对象。

(2)、把线程任务对象交给Thread处理。

(3)、调用Thread的start方法启动线程,执行任务

(4)、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。

        线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

        可以在线程执行完毕后去获取线程执行的结果。

缺点:

        编码复杂一点。 

import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; / 目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。 */ public class ThreadDemo3 { public static void main(String[] args) { // 3、创建Callable任务对象 Callable<String> call = new MyCallable(100); // 4、把Callable任务对象 交给 FutureTask 对象 // FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了 // FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果 FutureTask<String> f1 = new FutureTask<>(call); // 5、交给线程处理 Thread t1 = new Thread(f1); // 6、启动线程 t1.start(); Callable<String> call2 = new MyCallable(200); FutureTask<String> f2 = new FutureTask<>(call2); Thread t2 = new Thread(f2); t2.start(); try { // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。 String rs1 = f1.get(); System.out.println("第一个结果:" + rs1); } catch (Exception e) { e.printStackTrace(); } try { // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。 String rs2 = f2.get(); System.out.println("第二个结果:" + rs2); } catch (Exception e) { e.printStackTrace(); } } } / 1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型 */ class MyCallable implements Callable<String>{ private int n; public MyCallable(int n) { this.n = n; } / 2、重写call方法(任务方法) */ @Override public String call() throws Exception { int sum = 0; for (int i = 1; i <= n ; i++) { sum += i; } return "子线程执行的结果是:" + sum; } } 

三种方式的比较

方式

优点

缺点

继承Thread类

编程比较简单,可以直接使用Thread类中的方法

扩展性较差,不能再继承其他的类,不能返回线程执行的结果

实现Runnable接口

扩展性强,实现该接口的同时还可以继承其他的类。

编程相对复杂,不能返回线程执行的结果

实现Callable接口

扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果

编程相对复杂

三.线程Thread的常用方法

1. 当有很多线程在执行的时候,我们怎么去区分这些线程呢?

此时需要使用Thread的常用方法:getName()、setName()、currentThread()等。

Thread常用方法、构造器

方法名称

说明

String getName​()

获取当前线程的名称,默认线程名称是Thread-索引

void setName​(String name)

设置线程名称

public static Thread currentThread():

返回对当前正在执行的线程对象的引用

public static void sleep(long time)

让线程休眠指定的时间,单位为毫秒。

public void run()

线程任务方法

public void start()

线程启动方法

构造器

说明

public Thread(String name)


讯享网

可以为当前线程指定名称

public Thread(Runnable target)

把Runnable对象交给线程对象

public Thread(Runnable target ,String name )

把Runnable对象交给线程对象,并指定线程名称

讯享网 public class MyThread extends Thread{ public MyThread(){} public MyThread(String name){ super(name); } public void run(){ for(int i=0;i<50;i++){ System.out.println(getName()+"追逐王二的速度"+i+"km/h"); } } } public class ThreadDemo1 { public static void main(String[] args) { Thread t=new MyThread("张三"); Thread t1=new MyThread("李四"); t.start(); t1.start(); } } 

四.线程调度 

线程有两种调度模型 
        ●分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个钱程占用CPU的时间片 
        ●抢占式调度模型:  抢占式调度模型:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些 
        Java使用的是抢占式调度模型 
假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行一 条指令, 线程只有得到CPU时间片,也就是使用权, 才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的 

Thread类中设置和获取线程优先级的方法
●public final int getPriority0:返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级

五.线程控制

方法名      说明
static void sleep(long millis)     使当前正在执行的线程停留 (暂停执行) 指定的毫秒数
void join()  等待这个线程死亡
void setDaemon(booleanon)     将此线程标记为守护线程, 当运行的线程都是守护线程时,Java虚拟机将退出

static void sleep(long millis)    :可以让线程1秒内同时开始,然后停止。只有前后的争夺不会连续;

 public void run() { for (int i=0;i<50;i++){ System.out.println(getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }

void join() :“李渊”死了之后李世民和李建成才能开始夺位,所以join()是等设置的线程死亡其他线程才能工作。

讯享网public static void main(String[] args) { Thread t=new zh01("李渊"); Thread t1=new zh01("李世民"); Thread t2=new zh01("李建成"); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } t1.start(); t2.start(); }

void setDaemon(booleanon)  :设置一个主线程,其他守护线程等主线程死亡后,也慢慢停止运行

public class Zh04 { public static void main(String[] args) { zh01 td1 = new zh01 () ; zh01 td2 = new zh01 ( ) ; td1. setName( "关羽"); td2. setName("张飞"); //设置主线程为刘备 Thread . currentThread(). setName("刘备"); //设置守护线程 td1. setDaemon(true); td2. setDaemon(true); td1.start(); td2. start(); for(int i=0; i<10; i++) { System. out . println(Thread. currentThread() . getName()+":"+i); } } }

六.线程的生命周期:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5rW357u1aG9uZw==,size_20,color_FFFFFF,t_70,g_se,x_16

NEW(新建)

线程刚被创建,但是并未启动。

Runnable(可运行)

线程已经调用了start()等待CPU调度

Blocked(锁阻塞)

线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。

Waiting(无限等待)

一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒

Timed Waiting(计时等待)

同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。

Teminated(被终止)

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

七.线程同步

需求:某电影院正在上演一部大片,现有100张票在三个窗口销售,用线程模拟三个窗口的售票速度和票数情况,所以我们用sleep()方法模拟售票等待时机

讯享网package 多线程; public class Buypiao extends Thread{ public Buypiao(){ } public Buypiao(String name){ super(name); } private int piao=100; @Override public void run() { while (true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(piao>0){ System.out.println(Thread.currentThread().getName()+"销售还有"+piao+"张"); piao--; } } } package 多线程; public class Buypiaodomn { public static void main(String[] args) { Buypiao buy=new Buypiao("窗口1"); Buypiao buy1=new Buypiao("窗口2"); Buypiao buy2=new Buypiao("窗口3"); buy.start(); buy1.start(); buy2.start(); } } 

 9e1aba32f13f409d8bf974d2bd0fe729.png

我们发现在运行的时候出现了票数重复 :假设t1线程先抢到cpu的执行权,但是需要休息,这个时候t2抢到cpu的执行权,故此t2也开始执行,t3也是如此;最后才能减少票数

问题分析:

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准
●是否是多线程环境

是否有共享数据
●是否有多条语句操作共享数据
如何解决多线程安全问题呢?
●基本思想: 让程序没有安全问题的环境
怎么实现呢? .
●把多条语句操作共享数据的代码给锁起来, 让任意时刻只能有一个线程执行即可
●Java提供 了同步代码块的方式来解决


1.同步代码块:

格式:

synchronized (任意对象:相当于一把锁) { 
 
   
 

多条语句操作共享语句的代码

}

作用:把出现线程安全问题的核心代码给上锁。

原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

锁对象的规范要求

规范上:建议使用共享资源作为锁对象。

对于实例方法建议使用this作为锁对象。

对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步代码块是如何实现线程安全:

对出现问题的核心代码使用synchronized进行加锁

每次只能一个线程占锁进入访问

讯享网 public void run() { while (true) { synchronized (obj) { if (piao > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "销售还有" + piao + "张"); piao--; } } } }

cf9a5337366f409a85cf22452244d737.png

 问题解决思路:

假设 t1抢到了cpu的执行权,然后t1开始运行,t1休息的时候t2抢到cpu的执行权,因为代码上锁所以只能等待,等t1休息好,这段代码的锁就被释放了,运行完t2才开始执行,这个时候代码也会一步步递减

同步的好处和弊端
●好处:解决了多线程的数据安全问题
●弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

2.同步方法:

        就是把synchronized关键词加到方法上

格式:

        修饰符synchronized返回值类型 方法名(方法参数){ }

同步方法的锁对象:

        this

同步静态方法:就是把synchronized关键词加到静态方法上

格式:

        修饰符static synchronized返回值类型 方法名(方法参数){ }

同步方法的锁对象:

        类名.class

作用:把出现线程安全问题的核心方法给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

底层原理:

同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

如果方法是实例方法:同步方法默认用this作为的锁对象。

但是代码要高度面向对象! 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

3.lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象

方法名称

说明

public ReentrantLock​()

获得Lock锁的实现类对象

方法名称

说明

void lock()

获得锁

void unlock()

释放锁

 while (true) { lock.lock(); if (piao > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "销售还有" + piao + "张"); piao--; } lock.unlock(); } }

 八.线程池

1.概念

         线程池就是一个可以复用线程的技术。

2.不使用线程池的问题    

          如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

3.工作原理

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5rW357u1aG9uZw==,size_20,color_FFFFFF,t_70,g_se,x_16

4.如何得到线程池对象

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

ExecutorService-->ThreadPoolExecutor

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象 

 5.ThreadPoolExecutor构造器的参数说明

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5rW357u1aG9uZw==,size_20,color_FFFFFF,t_70,g_se,x_16

参数一:指定线程池的线程数量(核心线程): corePoolSize--->不能小于0

参数二:指定线程池可支持的最大线程数: maximumPoolSize--->最大数量 >= 核心线程数量

参数三:指定临时线程的最大存活时间: keepAliveTime--->不能小于0

参数四:指定存活时间的单位(秒、分、时、天): unit--->时间单位

参数五:指定任务队列: workQueue--->不能为null

参数六:指定用哪个线程工厂创建线程: threadFactory--->不能为null

参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler --->不能为null

6.线程池常见面试题:

临时线程什么时候创建啊?

        新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

        核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

作者有话说 

咋就是说本来只想写点的结果越写发现线程越神秘,要不是实力受限咋能写本书出来,线程真的有点强了,还有一些写了怕误导大家就不进行反面教材了!!! 谢谢大家支持!!!

小讯
上一篇 2025-03-04 08:10
下一篇 2025-01-15 19:42

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/15890.html