JAVA小知识30:JAVA多线程篇1,认识多线程与线程安全问题以及解决方案。(万字解析)

来        多线程,一个学起来挺难但是实际应用不难的一个知识点,甚至在很多情况下都不需要考虑,最多就是写测试类的时候模拟一下并发,现在我们就来讲讲基础的多线程知识。

一、线程和进程、并发与并行

1.1、线程和进程

线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。线程是一个进程中的实际执行单位,它负责当前进程中程序的执行。在一个进程中可以有多个线程,这些线程可以共享进程的资源,如堆和方法区。然而,每个线程都有自己的程序计数器、虚拟机栈和本地方法栈。因此,系统在产生一个线程或在不同线程间切换时的负担要小于进程,这也使得线程被称为轻量级进程

进程:进程是程序的基本执行实体。一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

1.2、并发与并行

并发:在同一时刻,有多个指令在单个CPU上交替执行 。
并行:在同一时刻,有多个指令在多个CPU上同时执行

二、实现多线程的三种方法 

2.1 继承Thread

多线程的第一种启动方式:

  1. 自己定义一个类继承Thread
  2. 重写run方法
    1. public class Student extends Thread{@Overridepublic void run() {// getName():获取当前线程名字for(int i=0;i<200;i++) {System.out.println("我是学生"+getName());}}
      }
  3. 创建子类的对象,并启动线程
    1. @Test
      void threadtest1(){Student s1 = new Student();Student s2 = new Student();s1.setName("1");//设置线程名s2.setName("2");//设置线程名s1.start(); //启动线程用start 而不是调用run方法s2.start();
      }

来看结果:

2.2、实现Runnable接口

 多线程的第二种启动方式:

  1. 自己定义一个类实现Runnable接口
  2. 重写里面的run方法
    1. public class Student implements Runnable{@Overridepublic void run() {// getName():获取当前线程名字for(int i=0;i<200;i++) {//Thread.currentThread():获取当前执行该线程的线程对象Thread thread = Thread.currentThread();System.out.println("我是老师"+thread.getName());}}
      }
  3. 创建自己的类的对象
  4. 创建一个Thread类的对象,并开启线程
    1. @Test
      void threadtest2(){Student s1 = new Student();Thread t1= new Thread(s1);Thread t2= new Thread(s1);t1.setName("1");t2.setName("2");t1.start();t2.start();
      }

我们来看结果:老师1与老师2交替执行

2.3、Callable

特点:可以获取到多线程运行的结果

  1. 创建一个类实现callable接口
  2. 重写call (是有返回值的。表示多线程运行的结果)
    1. public class Student implements Callable<Integer> {@Overridepublic Integer call() throws Exception {//求1-100的合int sum=0;for(int i=0;i<100;i++){sum+=i;}return sum;}
      }
  3. 创建类的对象(表示多线程要执行的任务)
  4. 创建Futureask的对象(作用:管理多线程运行的结果)
  5. 创建Thread类的对象,并启动(表示线程)
    1. void threadtest3() throws ExecutionException, InterruptedException {// 创建类的对象(表示多线程要执行的任务)Student s1 = new Student();// 创建Futureask的对象(作用管理多线程运行的结果)FutureTask<Integer> f1 = new FutureTask<Integer>(s1);// 创建Thread类的对象,并启动(表示线程)Thread t1 = new Thread(f1);t1.start();// 获取线程执行结果Integer i1 = f1.get();System.out.println(i1);
      }

答案为:5050 

看一下三种方法的优缺点

三、Thread中常用的成员方法

3.1、成员方法示例

3.1.1 get与set
public static void main(String[] args){Demo1 demo1 = new Demo1();Demo1 demo2 = new Demo1("设置了名字的线程2");Demo1 demo3 = new Demo1();demo3.setName("设置了名字的线程3");demo1.start();demo2.start();demo3.start();
}
public class Demo1 extends Thread{public Demo1(String name) {super(name);}public Demo1() {}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "@" + i);}}
}

3.1.2 currentThread()
public static void main(String[] args){Thread thread = Thread.currentThread();System.out.println(thread.getName());//main
}
3.1.3 getPriority()与setPriority()

线程分为10档 最小为1 最大为10 默认就是5吗,优先级不是绝对的 他是一个概率问题。

public static void main(String[] args){Thread thread = Thread.currentThread();System.out.println(thread.getPriority());//5
}
public static void main(String[] args){Thread thread = Thread.currentThread();thread.setPriority(10);System.out.println(thread.getPriority());//10
}
3.1.4 setDaemon()守护线程

守护线程就是:当非守护线程执行完毕之后,守护线程也会陆陆续续的停止,无论是否执行完毕,但是不会马上停止,会有个过程。

public class Demo1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "@" + i);}}
}
public class Demo2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(getName() + "@" + i);}}
}
public static void main(String[] args){Demo1 demo1 = new Demo1();Demo2 demo2 = new Demo2();demo1.setName("非守护线程");demo2.setName("守护线程");demo2.setDaemon(true);demo1.start();demo2.start();
}

这里的Demo2对象被设定为守护线程,他原本要执行1w次的,现在执行结果。

非守护线程执行结束之后,他只执行到了20次就结束了。

四、线程的生命周期

五、线程安全问题与解决方案

5.1 线程安全问题的发生

现在有一个订单秒杀,总共是100份,分三个平台来卖,我们看一下代码:

public class Goods extends Thread {// 加static就代表类对象共用一个countstatic int count=0;@Overridepublic void run() {while (count<100){count++;System.out.println(getName()+"正在卖第"+count+"个商品");}}
}
public static void main(String[] args) {Goods good1 = new Goods();Goods good2 = new Goods();Goods good3 = new Goods();good1.setName("某宝");good2.setName("某东");good3.setName("某多多");good1.start();good2.start();good3.start();
}

看结果:很明显,有问题,三家商城共卖一个?这能对吗,这肯定是不对的。这种问题就是线程并发的安全问题。

其包括有:
        1. 不同商铺卖同一个商品问题

        2. 超卖问题,也就是只有100个商品却卖了103个的问题

 5.2 同步代码块

把操作共享数据的代码锁起来:

特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

我们接下来看看通过同步代码块修改之后的代码:

// 加static就代表类对象共用一个count
static int count=0;
// 锁对象 锁对象非常的随意 但是切记需要唯一
static Object object = new Object();
@Override
public void run() {while (true){synchronized (object) {if(count<1000) {count++;System.out.println(getName() + "正在卖第" + count + "个商品");}else {break;}}}
}
public static void main(String[] args) {Goods good1 = new Goods();Goods good2 = new Goods();Goods good3 = new Goods();good1.setName("某宝");good2.setName("某东");good3.setName("某多多");good1.start();good2.start();good3.start();
}

既没有超卖问题,也没用同一个店铺卖同一个商品的问题 


 

5.3 同步方法 

就是把synchronized关键字加到方法上。

特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定:
        非静态:this
        静态:当前类的字节码文件对象

public class Goods extends Thread {
// 加static就代表类对象共用一个countstatic int count=0;// 锁对象 锁对象非常的随意 但是切记需要唯一static Object object = new Object();@Overridepublic void run() {while (true){if (extracted()) {break;}}}private synchronized boolean extracted() {if(count==1000) {return true;}else {count++;System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "个商品");}return false;}
}
public static void main(String[] args) {Goods gd = new Goods();Thread good1 = new Thread(gd);Thread good2 = new Thread(gd);Thread good3 = new Thread(gd);good1.setName("某宝");good2.setName("某东");good3.setName("某多多");good1.start();good2.start();good3.start();
}

5.4 Lock

        虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁。
        为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法
        1. void lock():获得锁
        2. void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法

ReentrantLock():创建一个ReentrantLock的实例。

注意:

在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。 java.concurrent.LockShouldWithTryFinallyRule.rule.desc
            

Positive example:Lock lock = new XxxLock();// ...lock.lock();try {doSomething();doOthers();} finally {lock.unlock();}

Negative example:Lock lock = new XxxLock();// ...try {// If an exception is thrown here, the finally block is executed directlydoSomething();// The finally block executes regardless of whether the lock is successful or notlock.lock();doOthers();} finally {lock.unlock();}

来看例子:

这里要注意的点是,首先 static Lock lock = new ReentrantLock();锁要加上static作为唯一的锁,第二是释放锁要在finally代码块中。

public class Teacher {public static void main(String[] args) {Student s1 = new Student();Student s2 = new Student();s1.setName("王老师");s2.setName("张老师");s1.start();s2.start();}
}
// 第二个类
public class Student extends Thread {static int studentID = 0;static Object o1 = new Object();static Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock();try {if (studentID < 100) {sleep(50);studentID++;System.out.println(getName() + "调用学号为:" + studentID + "的学生去干活");} else {break;}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}

 检查过输出之后,无超过100,无重复调用

六、死锁

我们上个章节讲到了线程安全问题以及解决方案,大体的解决方案就是加锁,那么加锁虽然会解决多线程并发的安全问题,同时也会造成一个新的问题:死锁

什么是死锁呢?打个比方:

两个人在一条窄窄的单行道上相向而行,每个人都在等待对方让路,以便自己能够通过。但因为两人都固执地站着不动,等待对方先让步,结果就是谁也无法前进,造成了一种僵持不下的局面。

在这个例子中,两个人就像是计算机中的两个进程,而这条单行道就像是计算机系统中的资源。如果每个进程都持有一个资源(比如一个人占据了道路的一部分),并且同时等待另一个进程释放它所持有的资源(另一个人占据的部分),而对方也在做同样的事情,那么双方就会陷入死锁状态,除非外部干预,否则他们都无法继续前进。

死锁并不是一个知识点,他是一个错误,需要我们在开发中避免。

那么死锁一般都是怎么发生的呢?

正常来讲一般都是锁里面套另外一个锁,会发生死锁现象 !

public class main {public class DeadlockExample {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Locked lock1");try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (lock2) {System.out.println("Thread 1: Locked lock2");}}});Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Locked lock2");try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (lock1) {System.out.println("Thread 2: Locked lock1");}}});t1.start();t2.start();}}
}

在这个例子中,我们有两个线程t1t2,它们分别尝试以不同的顺序锁定两个资源resource1resource2。每个线程首先锁定一个资源,然后稍作等待(模拟一些工作),接着尝试锁定另一个资源。

  • 线程t1首先锁定resource1,然后尝试锁定resource2
  • 线程t2首先锁定resource2,然后尝试锁定resource1

如果线程t1在线程t2锁定resource2之前锁定了resource1,并且线程t2在同一时间锁定了resource2,那么它们都会等待对方释放资源,从而形成死锁。每个线程都持有一个资源并且等待另一个线程释放它需要的资源,但没有线程能够继续执行,因为所需的资源被对方持有。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java学习十二—Java8特性之Optional类

一、简介 Java 8 引入了 Optional​ 类作为一种容器&#xff0c;可以用来显式地表示一个值存在或不存在。它解决了传统上可能会遇到的空指针异常问题&#xff0c;同时提供了一种更优雅的方式来处理可能为null的情况。 Java 8 中引入 Optional​ 类的背景可以从以下几个方面来理…

线程池概念的详解

前言&#x1f440;~ 上一章我们介绍了什么是定时器以及如何去实现一个定时器&#xff0c;今天我们来讲解在多线程中同样很重要的一个内容线程池 线程池的出现 线程池概念 标准库中的线程池 工厂模式 newCacheThreadPool方法 newFixedThreadPool方法 ThreadPoolExecutor…

Akamai+Noname强强联合 | API安全再加强

最近&#xff0c;Akamai正式完成了对Noname Security的收购。本文我们将向大家介绍&#xff0c;经过本次收购后&#xff0c;Akamai在保护API安全性方面的后续计划和未来愿景。 Noname Security是市场上领先的API安全供应商之一&#xff0c;此次收购将让Akamai能更好地满足日益增…

图像基础知识

图像卷积 卷积(convolution)是通过两个函数f和g生成第三个函数的一种数学算子,表征函数f与g经过翻转和平移的重叠部分的面积。 卷积概念是两个变量在某范围内相乘后求和的结果。图像处理中的卷积概念:数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积…

Java进阶学习|Day4.Java多线程,线程池

文章目录 了解多线程CPU进程(Process)线程多线程开发多线程优点 实现方式继承Thread类实现Runnable接口实现Callable接口 线程状态转换线程状态线程调度调整线程优先级线程睡眠线程等待线程让步线程加入线程唤醒 线程同步线程同步方式多线程间通信 线程池了解线程池定义常见接口…

可视化作品集(02):应急预警上的应用

应急预警领域是可视化大屏的一个重要应用场景&#xff0c;大屏展示的海量数据能为应急工作提供数据支持&#xff0c;本文带大家看看这类大屏的设计。 可视化大屏在应急和预警领域有广泛的应用&#xff0c; 1. 突发事件监测和应急响应&#xff1a; 可视化大屏可以实时展示突发…

顺序表的应用——通讯录的实现

前言 本篇博客将接着上次顺序表的内容进行拓展应用&#xff0c;这次来为大家介绍通讯录的实现&#xff0c;它就是基于顺序表的结构完成的&#xff1b;如果你对此感兴趣&#xff0c;请看下面的内容&#xff1b; 1.顺序表的应用 我们前面学过&#xff0c;顺序表可以存放任意类…

Java | Leetcode Java题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution {public String shortestPalindrome(String s) {int n s.length();int[] fail new int[n];Arrays.fill(fail, -1);for (int i 1; i < n; i) {int j fail[i - 1];while (j ! -1 && s.charAt(j 1) ! s.charAt…

ASP.NET Core Blazor 5:Blazor表单和数据

本章将描述 Blazor 为处理 HTML 表单提供的特性&#xff0c;包括对数据验证的支持。 1 准备工作 继续使用上一章项目。   创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。 inherits LayoutComponentBase<div …

论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型

论文全称&#xff1a;PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题&#xff1a;如何在检索增强式问答&#xff08;ReQA&#xff09;任务中&#xff0c;利用大型语言模型&#xf…

【C语言入门】初识C语言:掌握编程的基石

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言入门 &#x1f4d2;1. 选择…

单片机关键任务优先级的实现学习

与总体产品联调时&#xff0c;需要各个单机系统严格按照总体要求&#xff0c;进行数据输出&#xff0c;时间的偏差将出现系统异常&#xff0c;控制失败等不稳定情况产生&#xff0c;甚至影响到产品安全。 因此必须确保某些关键任务的优先执行。单片机任务优先级一般有两种方式…

[小试牛刀-习题练]《计算机组成原理》之指令系统

一、选择题 0.【指令-课本习题】某计算机按字节编址&#xff0c;指令字长固定且只有两种指令格式&#xff0c;其中三地址指令29条&#xff0c;二地址指令107条&#xff0c;每个地址字段为6位&#xff0c;则指令字长至少应该是&#xff08;A&#xff09; A.24位 B. 26位 C. 28位…

ctfshow web sql注入 web242--web249

web242 into outfile 的使用 SELECT ... INTO OUTFILE file_name[CHARACTER SET charset_name][export_options]export_options:[{FIELDS | COLUMNS}[TERMINATED BY string]//分隔符[[OPTIONALLY] ENCLOSED BY char][ESCAPED BY char]][LINES[STARTING BY string][TERMINATED…

Android系统层屏蔽弹出停止运行对话框

项目场景&#xff1a; 车载项目&#xff0c;ATC8257-Android9.0系统平台&#xff0c;福田汽车P3系列项目 项目使用高德公版地图前提是无法获得任何高德定制服务&#xff0c;每次刷完机去切换语言系统会弹出"高德地图已停止运行"弹窗&#xff0c;严重影响用户使用体…

【第三版 系统集成项目管理工程师】第6章 数据工程

持续更新。。。。。。。。。。。。。。。 【第三版】第六章 数据工程 6.1数据采集和预处理6.1.1 数据采集 P2346.1.2 数据预处理6.1.3 数据预处理方法1.缺失数据的预处理-P2352.异常数据的预处理-P2363.不一致数据的预处理-P2364.重复数据的预处理-P2365.格式不符数据的预处理…

UE5 03-物体碰撞检测

在你需要碰撞的物体上添加一个碰撞检测组件 碰撞预设 设置为NoCollision,这样移动过程中就不会有物理碰撞阻挡效果,只负责检测是否碰撞,比较难解释,如果学过Unity的话,可以把它理解成 Collision 为 Trigger

My sql 安装,环境搭建

以下以MySQL 8.0.36为例。 一、下载软件 1.下载地址官网&#xff1a;https://www.mysql.com 2. 打开官网&#xff0c;点击DOWNLOADS 然后&#xff0c;点击 MySQL Community(GPL) Downloads 3. 点击 MySQL Community Server 4.点击Archives选择合适版本 5.选择后下载第二个…

密码学复习

目录 基础 欧拉函数 欧拉函数φ(n)定义 计算方法的技巧 当a=a_1*a_2*……*a_n时 欧拉定理 剩余系 一些超简单密码 维吉尼亚 密钥fox 凯撒(直接偏移) 凯特巴氏(颠倒字母表) 摩斯密码(字母对应电荷线) 希尔(hill)密码 一些攻击 RSA 求uf+vg=1 快速幂模m^…

Python | Leetcode Python题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:def robRange(start: int, end: int) -> int:first nums[start]second max(nums[start], nums[start 1])for i in range(start 2, end 1):first, second second, max(fi…