当前位置: 首页 > news >正文

并发编程之线程基础

并发编程之线程基础

线程基础

线程概念

线程/进程区别

进程:加载上下文+执行程序+保存上下文;性能上的消耗;是资源分配的最小单位

线程:cpu执行+线程栈;属于执行任务的最小单位

线程组

ThreadGroup的提出是为了方便线程的管理,通过它可以批量设定一组线程的属性,比如setDaemon,设置未处理异常的处理方法,设置统一的安全策略等等;也可以通过线程组方便的获得线程的一些信息,尽量不要使用,会带来线程安全问题

启动线程
启动线程的方式只有一种
new Thread().start();
创建线程的方式有三种

第一种方式:通过继承Thread类

        Thread thread1 = new Thread01();thread1.start();

第二种方式:通过实现Runnable类

        Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Runnable-线程-"+Thread.currentThread().getName());}});thread2.start();

第三种方式:通过FutureTask类

        FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {System.out.println("Callable-线程-"+Thread.currentThread().getName());return null;}});Thread thread3 = new Thread(futureTask);thread3.start();

具体实例如下:


public class ThreadDemo1 {public static void main(String[] args) {test();}/*** 线程创建的三种方式*/private static void test() {//第一种方式:通过继承Thread类Thread thread1 = new Thread01();thread1.start();//第二种方式:通过实现Runnable类Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Runnable-线程-"+Thread.currentThread().getName());}});thread2.start();//第三种方式:通过FutureTask类FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {System.out.println("Callable-线程-"+Thread.currentThread().getName());return null;}});Thread thread3 = new Thread(futureTask);thread3.start();}
}class Thread01 extends Thread {@Overridepublic void run() {System.out.println("Thread01-线程-"+Thread.currentThread().getName());}
}
终止线程
stop

不建议使用thread.stop()方法,原因是:可能导致线程安全问题,破环一致性。

实例:

public static void test1() throws InterruptedException {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (this){try {i ++;Thread.sleep(3000);j++;} catch (InterruptedException e) {e.printStackTrace();}}}});thread1.start();Thread.sleep(1000);thread1.stop();System.out.println("i="+i+" j="+j);//打印结果:i=1 j=0  
}
interrupt
  1. interrupt方法并不会中断线程,只是给线程打上标记

  2. 如果目标线程在调用 wait()、wait(long)方法、join()、join(long, int)、join(long, int)、sleep(long, int)或sleep(long, int)等方法后,处于WAITING、Timed Waiting状态时,该线程被调用interrupt方法后,线程的WAITING、Timed Waiting状态将被清除,并抛出InterruptedException异常。

  3. park()\parkNanos方法执行后,线程也处于 WAITING、Timed Waiting,也会被唤醒,但是不会抛异常,且有很诡异的情况发生

  4. 如果目标线程是被I/O 或者NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。

  5. 如果以上条件都不满足,则会设置此线程的中断状态。

Thread.interrupted()//先返回当前线程的中断标志的值(true/false),然后将线程标志清除,更改为false
thread.currentThread().interrupt()//将线程设置中断标志,值为true
Thread.currentThread().isInterrupted()//判断线程标志
线程分类

线程分为守护线程、用户线程两种

守护线程:是指程序运行时,后台提供一种通信服务的线程,进程结束时,会杀死所有的守护线程。

用户线程:非守护线程的就是用户线程

注意:当没有用户线程运行时,进程结束

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2)在Daemon线程中产生的新线程也是Daemon的。

(3)守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

线程状态

线程的6种状态:New、Runnable、Blocked、Waiting、Timed Waiting、Terminated

1、New:尚未启动的线程的状态;

2、Runnable:准备就绪,可运行的线程状态,等待CPU调度;

3、Blocked:线程阻塞等待监视器释放锁的状态;

4、Waiting:线程等待的状态;

​ 不带时间参数的方式:Object.wait、Thread.join、LockSupport.park

5、Timed Waiting:具有指定等待时间的线程等待状态;

​ 带时间参数的方式:Object.wait(time)、Thread.sleep(time)、Thread.join(time)、 LockSupport.parkNanos(time)、LockSupport.parkUntil(time)

6、Terminated:终止线程的线程状态;

线程状态的关系图

image-20220119213555501

线程通讯机制

通信方式4种

通信方式有4种:

  1. 文件共享
  2. 网络共享
  3. 共享变量
  4. jdk提供的线程协调API
jdk提供的线程协调API

jdk提供的线程协作的API,如:

  1. suspend/resume
  2. wait/notify
  3. park/unpark
suspend/resume

作用:调用suspend挂起目标线程,通过resume可以恢复线程执行

被弃用的主要原因是,容易写出死锁的代码

实例代码:

image-20241023081007127

  • 死锁实例1

image-20241023080341273

  • 死锁实例2

image-20241023080502511

wait/notify

wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。

notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

注意1**:**虽然会wait自动解锁,但是对顺序有要求, 如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。

注意2**:**这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常。

示例

image-20241023081101648

死锁示例

image-20241023081205849

park/unpark

线程调用park则等待“许可” ,unpark方法为指定线程提供“许可(permit)”

>调用unpark之后,再调用park,线程会直接运行

> 提前调用的unpark不叠加,连续多次调用unpark后,第一次调用park后会拿到“许可”直接

运行,后续调用会进入等待。

示例代码

image-20241023081328009

死锁代码

image-20241023081344687

总结
协作方式机制方式1(锁synchronized)方式2(先唤醒,再挂起)备注
suspend/resume当某个线程的suspend()方法被调用时,当前线程被挂起,如果当前线程持有锁,它不会释放锁。即线程挂起时还持有锁死锁死锁弃用
wait/notify当某个线程中执行了wait方法时,线程被放入该对象的等待集合中,并且释放当前持有的对象锁不死锁死锁只能在sychronized语句块中使用,且调用wait、notify的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常
park/unpark线程调用park则等待“许可” ,unpark方法为指定线程提供“许可(permit)”。死锁不死锁

伪唤醒

伪唤醒是指线程并非因为notify、notifyall、unpark等api调用而意外唤醒,是更底层原因导致的。

警告!!!

​ 之前代码中用 if 语句来判断,是否进入等待状态,这样的做法是错误的!

​ 官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

伪唤醒实例
  1. 问题:两个线程对一个初始值为零的变量操作,实现一个线程加一,另一个线程减一,来十次。
  2. 问题:六个线程对一个初始值为零的变量操作,实现两个线程加一,另外两个线程减一,来十次。
public class Test {public static void main(String[] args) {final Number number = new Number();Thread thread1 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程A");Thread thread2 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程B");Thread thread3 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程C");Thread thread4 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程D");Thread thread5 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程E");Thread thread6 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程F");thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();thread6.start();}
}class Number {//        private volatile int  number = 0;private int  number = 0;public synchronized void add() throws InterruptedException {if (number != 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number++;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}public synchronized void reduce() throws InterruptedException {if (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}}
//打印结果
线程B---等待中
线程E---1
线程E---等待中
线程B---0
线程B---等待中
线程F---等待中
线程C---1
线程C---等待中
线程D---0
线程D---等待中
线程A---1
线程A---等待中
线程D---0
线程D---等待中
线程C---1
线程C---等待中
线程F---0
线程F---等待中
线程B----1
线程B----2
线程B----3
线程B----4
线程B----5
分析

期望结果:一个线程加1,得到1,然后等待,另一个线程减一,得到0

实际结果:又出现小于0,和大于1的情况,如上实例

我们以小于0的情况分析
假设:
B抢到了锁,进入reduce,这时 number ==0 ,B 等待,释放了锁;
然后D抢到了锁,进入reduce,这时 number == 0,D 等待,释放了锁;
然后A抢到了锁,进入add,设置number == 0,所以A向下走,number++,并唤醒了B、D
这时候坏事了,B和D都是在if内等待的,if判断过了后是不会再判断的,这时B、D都会向下执行,number- -
执行了两次,这时number == -1。
B、D其中有一个就是被虚假唤醒,这时解决虚假唤醒的方法就是将if改为while,因为while会在线程被唤醒后再次判断条件是否满足

解决伪唤醒

这时解决虚假唤醒的方法就是将 if 改为 while,因为while会在线程被唤醒后再次判断条件是否满足

实例

public class Test {public static void main(String[] args) {final Number number = new Number();Thread thread1 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程A");Thread thread2 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程B");Thread thread3 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程C");Thread thread4 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程D");Thread thread5 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程E");Thread thread6 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程F");thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();thread6.start();}
}class Number {//        private volatile int  number = 0;private int  number = 0;public synchronized void add() throws InterruptedException {while (number != 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number++;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}public synchronized void reduce() throws InterruptedException {while (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}}

ame()+“—等待中”);
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+“—:”+number);
//通知
this.notifyAll();;
}

public synchronized void reduce() throws InterruptedException {while (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;
}

}


http://www.xdnf.cn/news/596305.html

相关文章:

  • 『VUE』vue-quill-editor 添加超链接的同时为文字添加颜色(详细图文注释)
  • 有动画效果,但动画窗格里为空
  • 红黑树插入的旋转变色
  • Python |GIF 解析与构建(1):初步解析
  • SOC-ESP32S3部分:7-如何学习ESP32S3-IDF开发
  • Katoolin3 项目介绍:在 Ubuntu 上轻松安装 Kali Linux 工具
  • 【题解-洛谷】P9644 [SNCPC2019] Turn It Off
  • 1.2V超低功耗晶振:物联网设备续航提升的秘密武器
  • ThreadLocal底层原理解析
  • 比较结构的连通性
  • MySQL多线程备份工具mysqlpump详解!
  • 骰子游戏(2023睿抗省赛)
  • C++函数封装和绑定
  • 硬件,软件和进程
  • 过氧化物酶的邻近标记技术(APEX):最灵敏的蛋白互作方法
  • Python生成物理引擎的简单知识图谱
  • SOC-ESP32S3部分:6-任务看门狗
  • 101个α因子#18
  • 【Python/Tkinter】实现程序菜单
  • JVM部分内容
  • mybatisplus公共字段自动填充
  • 1.3 任务Task的说明(Xqt)
  • [Linux文件系统] “我的文件在哪?”FHS标准深度解析与核心目录实用指南
  • MVC和MVVM架构的区别
  • sqli-labs——二次注入
  • 常见的慢SQL优化方式
  • strlen和sizeof,const char *、char * const 和char []区别
  • 第二十九天打卡
  • 网络割接的详细流程和关键点
  • 关于常见日志的几种级别和格式