Java 线程 基础知识总结

article/2025/10/9 14:37:17

线程基础

很不严谨的说,线程是什么?线程就是为了让很多个东西并发执行,大大的提高程序执行的效率啊

三个非常重要的概念:

  • 程序:一组写好了的静态代码块(就我们写的那些代码玩意)
  • 进程:正在进行着的程序,即静态的代码执行起来了
  • 线程:是进程的小单元,或者说多个线程构成一个进程(线程的出现是因为进程满足不了人们的需求,于是进程被细化了)

线程的转换(五个较为简单的转换)可以通过这个图了解一下:
在这里插入图片描述
在Java中,

线程有三类:

  1. 主线程:系统线程,如Java中的Java虚拟机(主线程是最先执行的)
  2. 用户线程:Java中main函数执行的那些玩意
  3. 守护线程(精灵):比如Java中的GC(垃圾回收器,他是为主线程服务的,当系统启动后,GC就随之产生了;Java虚拟机断掉了,GC也就不干活了)

线程是操作系统层次的(又或者说是cpu层次的,进程怎么执行的,顺序是啥。。。这些都是靠cpu分配的)
不过线程怎么去用,Java的JDK已经给我们写好了
在这里插入图片描述
实现线程的过程(有两个方法):

  1. 自己描述一个类
  2. 实现线程的两个前提条件(继承Thread是最方便的,但是Java是单继承的,很可能会和其他的继承冲突;所以还可以通过实现Runnable接口,不过它要额外的通过写一个Thread类来执行start()方法):
    • 方法一:继承一个父类Thread
    • 方法二:实现一个接口Runnable
  3. 我们都必须重写run()方法,
    因为这个方法来源于cpu,是操作系统给JDK提供的接口,JDK将它包装成run(),其实我们执行的线程就是写在run()方法中的代码执行起来了
  4. new一个线程对象(我们是无法调用run()的,它只有cpu才可以调用,cpu分配时间碎片才能开始执行)
    我们只能调用start()方法(这个方法继承自Thread),它会使这个线程除了cpu资源的其它资源都得到满足,让它进入就绪状态,在就绪队列中等待着cpu执行它

(补充:为什么要这么麻烦呢?为什么不直接使用Thread类或者Runnable接口,直接在里面写线程的代码,但是run()方法是不允许有参数的,我们一般要执行的线程都要传参数,所以只能通过继承和实现来解决参数传递的问题)

一个例子】实现线程的两种方式

  • 继承一个父类 Thread,可以看到调用线程时写法较为简单
public class Running extends Thread {String name;public Running(String name){this.name = name;}@Overridepublic void run() {for(int i = 1; i <= 10; i++){System.out.println(name + "跑到了" + i + "米");}}public static void main(String[] args) {Running Lux = new Running("Lux");Running Ahri = new Running("Ahri");Running Annie = new Running("Annie");//从Thread类中继承过来的方法Lux.start();Ahri.start();Annie.start();}
}
  • 实现一个接口 Runnable,可以看到调用线程时写法复杂一些
public class Running implements Runnable {String name;public Running(String name){this.name = name;}@Overridepublic void run() {for(int i = 1; i <= 10; i++){System.out.println(name + "跑到了" + i + "米");}}public static void main(String[] args) {Running Lux = new Running("Lux");Running Ahri = new Running("Ahri");Running Annie = new Running("Annie");//Thread类中才有start()方法Thread thread1 = new Thread(Lux);thread1.start();Thread thread2 = new Thread(Ahri);thread2.start();Thread thread3 = new Thread(Annie);thread3.start();}
}

多次运行线程代码,发现线程无论执行顺序,还是开始执行时间都是随机的(实际上是操作系统通过算法调度和分配资源,JDK是不能影响它的)

我们只需要类中继承或实现线程,重写run()方法,最后调用start()方法就可以了

我们不要直接调用run()方法,这样就不是多线程了,只是按顺序执行的单线程!!!

模拟一个火车站售票小例子

  • 使用控制台输入输出,仅仅是为了简单玩一下线程,例子很简陋
  • 三个类
    • 车票
    • 售票系统12306
    • 售票窗口
  • 代码:github
  • 效果展示:
从广州北站售出:[ 北京11 --> 深圳11 : 150.0 ]
从广州西站售出:[ 北京10 --> 深圳10 : 125.0 ]
从广州南站售出:[ 北京12 --> 深圳12 : 175.0 ]
从广州西站售出:[ 北京14 --> 深圳14 : 225.0 ]
从广州南站售出:[ 北京15 --> 深圳15 : 125.0 ]
...
...
对不起广州南站窗口车票已售完
对不起广州西站窗口车票已售完
对不起广州北站窗口车票已售完

多线程同时读取一个文件

  • 题目:有一个26字节的文件 test.txt,里面按顺序存储着26个小写英文字母,使用3个线程同时读取
  • 使用控制台输入输出,仅仅是为了简单玩一下线程与IO,例子很简陋
  • 分析:
    • 使用字节型输入流,使用skip()方法
    • 1号线程:读取1-10字节
    • 2号线程:读取11-20字节
    • 3号线程:读取21-26字节
  • 代码:github
  • 效果展示:
Thread-0读取:a
Thread-2读取:u
Thread-1读取:k
Thread-2读取:v
Thread-0读取:b
Thread-2读取:w
...
...

生产消费者模型

生产消费者模型

我创建一个生产者向仓库添加物品
两个消费者向仓库中拿走物品

在多线程并发进行中会出现这么一种问题:
在这里插入图片描述
如果仓库只剩一个物品时,出现了这么一个顺序

  1. 消费者1进行判断(还剩一个)
  2. 消费者2进行判断(还剩一个)
  3. 消费者1执行get方法
    • get方法作用是:获取物品
  4. 消费者2执行get方法(这时候就出现问题了,虽然判断时仓库还有一个可以拿;但是到执行get方法时,仓库是空的,那么问题出现了:多线程并发抢夺资源
  • 生产者消费者模型
    • 模拟上述描述的问题,并且代码中不涉及其余线程操作,只会出现生产者消费者模型的问题
    • 三个类:Producer、Consumer、WareHouse
    • 分别代表:生产者、消费者、仓库
  • 代码:github
  • 不出所料,在执行的过程中出现问题了
    在这里插入图片描述两个消费者,成功演示出了线程安全问题;两个消费者并发访问,可能产生抢夺资源的问题;所以多个线程并发执行的时候,有安全隐患

synchronized

  • 生产者消费者模型 解决办法
    • 让仓库被线程访问的时候,仓库对象被锁定(即仓库对象只能被一个线程访问,其它的线程处于等待状态)
    • 使用一个特征修饰符即可synchronized,又称为线程安全锁,表示同步的意思,作用:一个时间点只有一个线程访问
    • 给仓库提供的获取物品的方法(get)加上线程安全锁,可以避免多线程并发抢夺资源

synchronized 有两种写法:

  1. synchronized关键字,放在方法的结构上(特征修饰符),其实它锁定的是synchronized修饰方法所在的对象,由调用该方法的线程锁定
public synchronized void test(){代码}
  1. synchronized关键字,放在方法(/构造方法/块)内部
public void test(){很多代码synchronized(对象){   //只有方法执行到这里的时候才被锁定//锁定哪个对象呢?可以由synchronized()的参数决定某些代码}很多代码
}

第二种写法性能更加棒;而且更为灵活,给内部的synchronized(){}的参数传对象,不仅可以由调用方法的线程锁定,还可由其他线程对象来锁定

线程对象改变引起的异常

  • 如果线程调用的方法中有wait()notify()/notifyAll()方法,但是没有加synchronized线程安全锁时会出现一种异常:java.lang.IllegalMonitorStateExceptionr

  • 注意:wait和notify需要成对出现,或者wait和notifyAll成对出现,否则可能出现假死锁

    • wait() 线程等待
    • notify() 唤醒某个线程
    • notifyAll() 唤醒所有线程
    • wait() notify() notifyAll()都是Object的方法,但和synchronized关键字一样,也是哪个线程调用则对哪个线程起作用

在生产者消费者模型代码基础上进行修改,仓库的获取物品与存放物品的方法不加线程安全锁,并且使用线程等待与线程唤醒方法
在这里插入图片描述
这就是张冠李戴的字面解释了,当访问仓库的生产者线程等待,告知生产者的这一刹那,对象变成了另一个线程:该等待的没有等待,不该等待的等待了

join

  • join()方法也是Thread类中的方法,挺有用的
  • 可以让两个并行的线程变成单线程

线程创建好之后,我们不能控制线程执行的先后顺序,我们只能让线程进入就绪状态

例子】首先来回顾一下线程并发执行的情况,以线程ThreadOne与ThreadTwo为例:

ThreadOne one = new ThreadOne();
ThreadTwo two = new ThreadTwo();
one.start();
two.start();
  • one执行,one结束,two执行,two结束
  • one执行,two执行,one结束,two结束
  • one执行,two执行,two结束,one结束

例子】如果有两个线程ThreadOne和ThreadTwo,我想让ThreadTwo加入到ThreadOne中,可以这么写:

ThreadOne

public class ThreadOne extends Thread {public void run() {System.out.println("thread-one start");ThreadTwo two = new ThreadTwo();two.start();try {two.join();//线程2加入到线程1中} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread-one end");}
}

ThreadTwo

public class ThreadTwo extends Thread{public void run() {System.out.println("thread-two start");try {Thread.sleep(5000);//睡眠5秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread-two end");}
}

在main中,调用ThreadOne线程的start()方法:
在这里插入图片描述

  • 这里用的是join()的无参方法,thread-one启动后,必须要等着thread-two执行完,才能往后面执行

  • join(millis)还有一个有参重载方法

将ThreadOne类中的 two.join() 改为 two.join(4000)

thread-one启动后,不是必须等待着thread-two执行完,而是等待4000毫秒如果thread-two还没执行完,thread-one就继续往后执行
在这里插入图片描述

join源码结合例子分析

【join源码】

  • 无参的 join() 方法:
public final void join() throws InterruptedException {join(0);//还是会调用有参的join方法
}
  • 有参的 join() 方法:
public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) { //这就是调用无参join时的执行代码while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}
}
  • ThreadOne类中调用的无参 two.join()
    • 只要 ThreaTwo 活着,那么 ThreadOne 就一直是等待状态
while (two.isAlive()) {two.wait(0);
}
  • ThreadOne类中调用的有参且参数大于0 two.join(4000)
    • ThreaTwo 活着的期间,ThreadOne 等待millis毫秒后打破循环,即不再等待了
while (two.isAlive()) {long delay = millis - now;if (delay <= 0) {break;}two.wait(delay);now = System.currentTimeMillis() - base;
}

join遇上线程安全锁

  • 感受一下 synchronized 的强大吧
    • 三个线程类:ThreadOne、ThreadTwo、ThreadThree
    • ThreadOne 中调用 ThreadTwo,并 two.join(2000);,不过 ThreadTwo 需要执行5秒
    • ThreadTwo 中调用 ThreadThree,并将自身通过参数传给 ThreadThree
    • ThreadThree 通过 synchronized 将 ThreadTwo 锁起来10秒
  • 代码:github
  • 执行效果
    在这里插入图片描述
  1. one启动
  2. two启动
  3. three启动
  4. two就join进one中了(two要执行5000毫秒,但是one只给了two2000毫秒)
  5. 2000毫秒之后,one想要把two从自己的线程中剔除掉;但是发现,two已经不在自己的手中,two已经被three锁定(要被锁定10000毫秒)
  6. one只能等待three将two释放后才能剔除掉two

所以,锁是非常强大滴

死锁

  • synchronized非常厉害,一旦线程被锁定,不释放的情况,其它的线程都需要等待
  • 如果锁没用好,有可能产生死锁的问题(互相都想要对方的资源,却都得不到满足)

这么说吧,
Ahri手里有a资源,然后需要b资源
Lux手里有b资源,然后需要a资源
而且,a资源被Ahri独占了,其它进程拿不到;b资源被Lux独占了,其它进程都拿不到
那么:
Ahri得不到Lux的b资源
Lux得不到Ahri的a资源
就会这样:
Ahri和Lux一直等着对方释放资源

这就是死锁

哲学家进餐问题

哲学家进餐问题 是一个著名的死锁问题:
在这里插入图片描述
四个人在餐桌上吃饭,但是只有四只筷子,
四个人同时拿筷子,
而且要求,每一个人先拿左手边的筷子,然后拿右手边的筷子

所以嘛,有可能他们四个人拿筷子的速度恰好一样,都拿了左手边的筷子,于是僵持住了

  • 代码:github

避免死锁的方法:

  • 礼让----产生时间差
  • 不要产生共用的问题

有趣的Timer类

  • 计时器/定时器是JDK已经写好了的线程类
  • java.util包,Timer

小例子】用Timer类模拟一个短信轰炸的效果(每隔一段时间发送信息)

Timer有这么一个方法schedule(),有四个重载方法
在这里插入图片描述
对上面出现的参数的解释

  1. TimerTask task表示一个任务,以字符串形式表示
    TimerTask是一个抽象类,new不了的,不过可以使用匿名内部类对象
  2. Date timeDate firstTime表示起始时间(到了起始时间,任务开始执行)
  3. long delay表示延迟时间(及延迟给的时间后,任务开始执行)
  4. long period周期,表示多长时间后再干一次,是一个循环

短信轰炸

//导包
//...
public class TestTimer {private int count = 1;//记录轰炸次数ArrayList<String> userBox = new ArrayList<>();//存储众多个人信息{userBox.add("a"); userBox.add("b"); userBox.add("c"); userBox.add("d");}启动一个小线程,记录时间,可以每隔一段时间去做一件事情public void test() throws ParseException {System.out.println("开始啦");Timer timer = new Timer();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date firstTime = sdf.parse("2020-07-27 16:30:00");timer.schedule(new TimerTask() {public void run() {System.out.println("第" + count++ + "次执行");for(int i = 0; i < userBox.size(); i++){System.out.println("给" + userBox.get(i) + "发送了一条消息:陌生人祝你幸福");}System.out.println("做了点坏事儿 真开心~~~");}},firstTime,3000);}public static void main(String[] args) {TestTimer demo = new TestTimer();try{demo.test();}catch (ParseException e){e.getErrorOffset();}}}

在这里插入图片描述


http://chatgpt.dhexx.cn/article/C8tLMiRw.shtml

相关文章

Java多线程(超详解)

目录 1. 线程简介 1.1 程序 1.2 进程 1.3 线程 1.4 多线程 1.5 普通方法调用和多线程 2. 线程创建 2.1 继承Thread类 2.2 实现Runnable接口 2.3 实现Callable接口&#xff08;了解&#xff09; 2.4 网图下载 2.4.1 通过继承Thread类实现网图下载 2.4.2 通…

java 线程详解

一、线程的基本概念 一个程序最少需要一个进程&#xff0c;而一个进程最少需要一个线程。关系是线程–>进程–>程序的大致组成结构。所以线程是程序执行流的最小单位&#xff0c;而进程是系统进行资源分配和调度的一个独立单位。 一个线程就是在进程中的一个单一的顺序…

JAVA多线程详解(超详细)

目录 一、线程简介1、进程、线程2、并发、并行、串行3、进程的三态 二、线程实现1、继承Thread类2、实现Runnable接口3、实现Callable接口&#xff08;不常用&#xff09; 三、线程常用方法1、线程的状态2、线程常用方法 四、多线程1、守护&#xff08;Deamon&#xff09;线程2…

Java多线程(超详细!)

1、什么是进程&#xff1f;什么是线程&#xff1f; 进程是:一个应用程序&#xff08;1个进程是一个软件&#xff09;。 线程是&#xff1a;一个进程中的执行场景/执行单元。 注意&#xff1a;一个进程可以启动多个线程。 eg. 对于java程序来说&#xff0c;当在DOS命令窗口中…

count/count if函数的基本用法

count函数&#xff0c;用来计算单元格的数的个数&#xff0c;只是用来计数&#xff0c;并且只有只记录数子的个数&#xff0c;文本的个数是不被记录的。 但是很少会用到单纯的count函数&#xff0c;往往在工作中计数是带有条件的。就会用到countif函数 COUNTIF函数需要注意的点…

EXCEL COUNTIF()的一些奇特的用法

文章目录 前言一、统计第几次重复二、统计不重复的数量三、通配符模糊统计四、防止重复录入五、忽略错误值或空值统计六、重复值填充背景色总结 前言 日常工作中需要度娘很多知识点或者方法&#xff0c;但每次用了就忘&#xff0c;下次遇到就需要继续度娘&#xff0c;故在此记…

Excel多条件计数——COUNTIFS【获奖情况统计】

问题描述 当前&#xff0c;我们需要对表格中的获奖情况进行统计 奖励级别&#xff1a;院级、校级、国家级、国际级奖励内容&#xff1a;特等奖、一等奖、二等奖、三等奖、优胜奖 功能要求 对所有奖励级别进行统计根据级别&#xff0c;计算内容数量 当有人的选项内容如下时 …

如何在Microsoft Excel中使用COUNTIF函数

COUNTIF 是一个 Excel 函数,用于对满足单个条件的区域中的单元格进行计数。COUNTIF可用于计算包含日期、数字和文本的单元格。COUNTIF 中使用的条件支持逻辑运算符(>、<、<>、=)和通配符(*、?)进行部分匹配。 例如,我们想计算包含 Google或 Facebook 的单元…

COUNT函数的使用

一、问题描述 今天在随手练习sql的时候&#xff0c;发现count查出来的数量和实际的数量不对&#xff0c;下面是我查询的sql 我想看看suitName字段一共有多少种数据 SELECTCOUNT(DISTINCT suitName) AS suitNameNum FROMrace_goods 得到的结果是suitName条数是22条&#xff0c…

countif是什么意思,如何运用?

countif是什么意思&#xff1f;countif函数是excel中对指定区域中符合指定条件的单元格计数的一个函数&#xff0c;简单来说就是算出某个参数的数量。那么&#xff0c;countif函数具体是如何运用的呢&#xff1f;小编分为两种情景为大家总结了操作步骤。 情景一&#xff1a;计算…

excel通过sumproduct和countifs不重复计数(数据中包含空白单元)

1. 常规情况&#xff0c;数据中不包含空白单元格&#xff0c;如下图&#xff1a; SUMPRODUCT((A2:A24E2)*(B2:B24F2)*(1/COUNTIFS(A2:A24,A2:A24,B2:B24,B2:B24,C2:C24,C2:C24))) 2. 数据中包含空白单元格&#xff0c;如下图&#xff1a; 数据中包含空白单元的情况&#xff0c…

countif怎么读(countif怎么读)

SUM,AVERAGE,MAX,MIN,PRODUCT,CUONT,RANK,IF,SUMIF,COUNTIF怎么读呀&#xff01;谢谢 SUM(桑母) 求和函数&#xff1b; AVERAGE(哎五瑞之) 求平均数函数&#xff1b; MAX(麦克斯) 最大值函数&#xff1b; MIN(民) 最小值函数&#xff1b; PRODUCT(普若达克特) 求积函数&#xf…

[Excel常用函数] countif countifs函数

countif函数 1.countif函数的含义 在指定区域中按指定条件对单元格进行计数&#xff08;单条件计数&#xff09; 2.countif函数的语法格式 countif&#xff08;range&#xff0c;criteria&#xff09; 参数range 表示条件区域——对单元格进行计数的区域。 参数criteria …

Microsoft Excel 教程:如何在 Excel 中使用 COUNTIF 函数?

欢迎观看 Microsoft Excel 教程&#xff0c;小编带大家学习 Microsoft Excel 的使用技巧&#xff0c;了解如何在 Excel 中使用 COUNTIF 函数。 COUNTIF 是一个统计函数&#xff0c;用于统计满足某个条件的单元格的数量&#xff1b;例如&#xff0c;某个姓名在单元格区域中出现…

[Excel函数] COUNT函数 | COUNTIF函数 | COUNTIFS函数

1.COUNT函数 语法: COUNT(value1,[value2],...) COUNT函数用于计算区域中包含数字的单元格的个数 (不统计文本性数字) COUNT函数很少单独使用&#xff0c;一般和其他函数嵌套使用 案例: 统计数字的个数 注意: A13该单元格数字为文本格式&#xff0c;COUNT函数不统计文本性数字 …

Excel如何使用COUNTIF函数

COUNTIF函数是我们在工作中经常使用的函数&#xff0c;今天就给大家分享一下如何使用countif函数。 1、COUNTIF函数主要用于对区域中满足单个指定条件的单元格进行计数。它的语法结构是COUNTIF(统计区域&#xff0c;统计条件) 注意要点 1.COUNTIF不区分大小写。 2.在条件中可…

sum与countif、countifs函数套用

1、sum与countif套用&#xff0c;实现多条件计数求和 公式&#xff1a;sum(countif(条件区域,{"条件1","条件2","条件3"})) 示例&#xff1a; 2、sum与countifs套用&#xff0c;实现多区域多条件计数求和 公式&#xff1a;sum(countifs(条件区…

COUNTIFS函数使用

COUNTIFS函数是Excel中的一个统计函数&#xff0c;用来计算多个区域中满足给定条件的单元格的个数&#xff0c;可以同时设定多个条件。 countifs(criteria_range1,criteria1,criteria_range2,criteria2,…) 参数说明 criteria_range1&#xff1a;为第一个需要计算其中满足某个…

excel 第9讲:countif函数

一、使用Countif函数 1 、Count函数 2、Countif函教语法&#xff1a;countif(range,criteria) 3 、Countif函数计算数值区间 4 、countif函教超过15位字符时的错误 二、常见应用示例 1、在数据区域中寻找重复数据 2、在数据有效性中使用countif函数 3、在条件格式中使用…

COUNTIF函数的使用方法

今天给大家介绍一下countif函数的使用方法&#xff0c;以及实例演示一下如何使用countif函数统计不重复值个数、按部门添加序号、统计不同区间工资人数。 COUNTIF函数主要用于对区域中满足单个指定条件的单元格进行计数。它的语法结构是COUNTIF(统计区域&#xff0c;统计条件)…