java多线程(超详细讲解)下篇

本章继续讲多线程

目录

  • 一、线程同步
    • 1、为什么需要线程同步
  • 二、如何实现线程同步
    • 1、同步代码块
    • 2、同步方法
    • 3、线程同步特征
  • 三、线程安全的类型
    • 1、ArrayList是常用的集合类型,它是否线程安全的呢?
    • 2、对比 Hashtable和HashMap
      • 1、是否线程安全
      • 2、效率比较
    • 3、对比StringBuffer和StringBuilder
  • 四、线程的状态转换
  • 总结

一、线程同步

当多个线程共享数据时,由于CPU负责线程的调度,所以程序无法精确地控制多线程的交替次序。如果没有特殊控制,则多线程对共享数据的修改和访问将导致数据的不一致。

1、为什么需要线程同步

上一章学习的线程都是独立且异步运行的,也就是说每个线程都包含了运行时所需要的数据或方法,不必关心其他线程的状态和行为。但是经常会有一些同时运行的线程需要操作共同数据,此时就要考虑其他线程的状态和行为;否则,不能保证程序运行结果的正确性。

示例:

public class Storage {public final int MAX_COUNT = 3;//最大库存三车public int getCount() {return count;}public void setCount(int count) {this.count = count;}private int count = 0;//库存量//对外向果商发货public void get() {if (count > 0) {count--;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}//2.显示数据System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");}}//果园向仓库供货public void put() {if (count == 0) {count = MAX_COUNT;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据}}
}class BusiThread implements Runnable {Storage storage;public BusiThread(Storage storage) {this.storage = storage;}public void run() {while (true) {this.storage.get();//仓库供应一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class FarmerThread implements Runnable {Storage storage;public FarmerThread(Storage storage) {this.storage = storage;}public void run(){while (true) {this.storage.put();//向仓库运送一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class Test7{public static void main(String[] args) {Storage storage=new Storage();//创建仓库类对象Thread busi1=new Thread(new BusiThread(storage),"莲花超市");Thread busi2=new Thread(new BusiThread(storage),"果果香水果店");Thread farmer=new Thread(new FarmerThread(storage),"果农老王");farmer.start();busi1.start();busi2.start();}
}

在以上代码中,变量count存储现有仓库库存;get()方法实现当仓库库存大于零时,对外向果商或商超发货,每次发货一车;put()方法实现当仓库库存为零时,由果园的果农向仓库供货,每次供货三车。为了展示更多的数据方便观察效果,在示例代码中,FarmerThread类和BusiThread类中存在死循环。

运行结果:

果农老王:已向仓库发送1车水果
果果香水果店:采购了第2车水果
莲花超市:采购了第2车水果
莲花超市:采购了第0车水果
果农老王:已向仓库发送2车水果
莲花超市:采购了第1车水果
果果香水果店:采购了第1车水果
果果香水果店:采购了第0车水果
莲花超市:采购了第0车水果
果农老王:已向仓库发送1车水果
果果香水果店:采购了第2车水果
莲花超市:采购了第2车水果
莲花超市:采购了第1车水果
果果香水果店:采购了第2车水果
果农老王:已向仓库发送1车水果
莲花超市:采购了第3车水果
果果香水果店:采购了第0车水果

从运行结果可以发现,运行结果中存在如下数据问题:

  1. 不是从第一车水果开始供货。
  2. 出现果果香水果店和莲花超市共同采购同一车水果的情况。
  3. 果农老王应该运输三车水果到仓库,但数据显示只发送两车。

多个线程共同操作同一共享资源会带来数据不安全问题。具体原因是在Storage类中存在以下情况:

  1. 存在多个线程共同操作的变量count。
  2. 在get()方法和put()方法中,存在修改数据和显示数据两步操作。

二、如何实现线程同步

当两个或多个线程需要访问同一资源时,需要以某种顺序来确保资源某一时刻只能被一个线程使用,这被称为线程同步。线程同步相当于为线程中需要一次性完成不允许中断的操作加上一把锁,从而解决冲突。

1、同步代码块

代码块即使用“{}”括起来的一段代码,使用synchronized关键字修饰的代码块被称为同步代码块。

语法结构:

synchronized(obj){//需要同步的代码
}

示例:

package d10;public class Storage2 {public final int MAX_COUNT = 3;//最大库存三车public int getCount() {return count;}public void setCount(int count) {this.count = count;}private int count = 0;//库存量//对外向果商发货public void get() {synchronized (this) {if (count > 0) {count--;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}//2.显示数据System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");}}}//果园向仓库供货public void put() {synchronized (this) {if (count == 0) {count = MAX_COUNT;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据}}}
}class BusiThread2 implements Runnable {Storage2 storage;public BusiThread2(Storage2 storage) {this.storage = storage;}public void run() {while (true) {this.storage.get();//仓库供应一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class FarmerThread2 implements Runnable {Storage2 storage;public FarmerThread2(Storage2 storage) {this.storage = storage;}public void run() {while (true) {this.storage.put();//向仓库运送一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class Test8 {public static void main(String[] args) {Storage2 storage = new Storage2();//创建仓库类对象Thread busi1 = new Thread(new BusiThread2(storage), "莲花超市");Thread busi2 = new Thread(new BusiThread2(storage), "果果香水果店");Thread farmer = new Thread(new FarmerThread2(storage), "果农老王");farmer.start();busi1.start();busi2.start();}
}

运行结果:

果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果

2、同步方法

如果一个方法的所有代码都属于需同步的代码,那么这个方法定义处可以直接使用synchronized关键字修饰,即同步方法。

语法结构:

package d10;public class Storage3 {public final int MAX_COUNT = 3;//最大库存三车public int getCount() {return count;}public void setCount(int count) {this.count = count;}private int count = 0;//库存量//对外向果商发货public synchronized void get() {if (count > 0) {count--;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}//2.显示数据System.out.println(Thread.currentThread().getName() + ":采购了第" + (MAX_COUNT - count) + "车水果");}}//果园向仓库供货public synchronized void put() {if (count == 0) {count = MAX_COUNT;//1.修改数据try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":已向仓库发送" + count + "车水果");//显示数据}}
}class BusiThread3 implements Runnable {Storage3 storage;public BusiThread3(Storage3 storage) {this.storage = storage;}public void run() {while (true) {this.storage.get();//仓库供应一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class FarmerThread3 implements Runnable {Storage3 storage;public FarmerThread3(Storage3 storage) {this.storage = storage;}public void run(){while (true) {this.storage.put();//向仓库运送一车水果try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class Test9{public static void main(String[] args) {Storage3 storage=new Storage3();//创建仓库类对象Thread busi1=new Thread(new BusiThread3(storage),"莲花超市");Thread busi2=new Thread(new BusiThread3(storage),"果果香水果店");Thread farmer=new Thread(new FarmerThread3(storage),"果农老王");farmer.start();busi1.start();busi2.start();}
}

运行结果:

果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果
果农老王:已向仓库发送3车水果
果果香水果店:采购了第1车水果
莲花超市:采购了第2车水果
果果香水果店:采购了第3车水果

3、线程同步特征

所谓线程之间保持同步,是指不同的线程在执行之间以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制,线程同步具有以下特征:

  1. 当多个并发线程访问同一对象的同步代码块或同步方法时,同一时刻只能有一个线程运行,其他线程必须等待当前线程运行完毕后才能运行。
  2. 如果多个线程访问的不是同一共享资源,则无需同步。
  3. 当一个线程访问Object对象的同步代码块或同步方法时,其他线程仍可以访问该Object对象的非同步代码块及非同步方法。
    综上所述,synchronized关键字就是为当前的代码块声明一把锁,获得这把锁的线程可以执行代码块里的指令,其他线程只能等待取锁,然后才能执行相同的操作。

三、线程安全的类型

若程序所在的进程中有多个线程,而当这些线程同时运行时,每次的运行结果和单线程的运行结果是一样的,而且其他变量的值夜和预期相同,那么当前程序就是线程安全的。
一个类在被读线程访问时,不管运行时对这些线程有怎样的时序安排,它必须是以固定的、一致的顺序执行,这样的类型被称为线程安全的类型。

1、ArrayList是常用的集合类型,它是否线程安全的呢?

答案是ArrayList是非线程安全的类型。
而ArrayList集合添加一个元素主要完成如下两步操作:

  1. 判断列表容量是否足够,是否需要扩容。
  2. 将元素添加到列表的元素数组里。
    以上两步操作并非不可分割,这样也就出现了导致线程不安全的隐患。在多个线程执行add()方法进行添加元素操作时,可能会导致elementData数组越界。

2、对比 Hashtable和HashMap

1、是否线程安全

Hashtable是线程安全的,其方法时同步的,可查看Hashtable类型源码中操作数据的方法为同步方法。
而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable。
如果使用HashMap,就要自行增加同步处理。

2、效率比较

由于Hashtable是线程安全的,其方法是同步的,而HashMap是非线程安全的,重速度,轻安全,所以当只需要单线程时,使用HashMap的执行速度要搞过Hashtable。

3、对比StringBuffer和StringBuilder

StringBuffer和StringBuilder都可用来存储字符串变量,是可变的对象。它们的区别是StringBuffer是线程安全的,而StringBuilder是非线程安全的。因此,在单线程环境下StringBuilder执行效率更高。

四、线程的状态转换

线程的状态转换就是利用了Thread常用方法,
在Java语言使用Thread类及其子类的对象表示线程,新建的线程通常会在五钟状态中转换,即新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡。
这里就不多讲了!!!

总结

多线程允许程序员编写出可最大程度利用CPU的高效程序。

  1. 在Java程序启动时,一个线程立刻运行,该线程通常被成我饿程序的主线程。主线程是产生其他子线程的线程。
  2. 通常,主线程必须最后完成运行,因为它执行各种关闭动作。

可通过两种方式创建线程。

  1. 声明一个继承了Thread类的子类,在此子类中,重写Thread类的run()方法。
  2. 声明一个实现Runnable接口的类,然后实现run()方法。

每一个线程均会处于新建、就绪、运行、阻塞、死亡五钟状态之一。
在Java实现的多线程应用程序中,可以通过调用Thread类中的方法实现对线程类对象的操作。

  1. 调整线程的优先级:在同等情况下,优先级高的线程会获得较多的运行机会,优先级低的线程则相反。Java线程优先级用1~10的整数表示。
  2. 线程休眠:sleep(long millis)方法使线程转到阻塞状态。
  3. 线程的强制运行:join()方法可以让某一线程强制运行。
  4. 线程礼让:yield()方法,暂停当前正在执行的线程类对象,把执行机会让给相同或更高优先级的线程。

当多个线程类对象操作同一共享资源时,要使用synchronized关键字进行资源的同步处理,可以使用同步代码块或同步方法实现线程同步。

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

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

相关文章

javaSwing愤怒的小鸟

一、简介 游戏名称是“愤怒的小鸟”,英文称为“AngryBird”。 “愤怒的小鸟”是著名游戏公司Rovio偶然间开发出来的益智游戏,从2009年12月上市到iOS。,讲述了鸟类和猪因为猪偷鸟蛋反生的一系列故事。游戏的类型版本是横向版本的水平视角&…

怎么在Linux系统下Docker部署Excalidraw白板工具并实现无公网IP远程访问?

文章目录 1. 安装Docker2. 使用Docker拉取Excalidraw镜像3. 创建并启动Excalidraw容器4. 本地连接测试5. 公网远程访问本地Excalidraw5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Ubuntu系统使用Docker部署开源白板工具Excal…

java算法题每日多道四

1. 两数之和 题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你…

C++临时变量

本博客将讲述我学习过程中对临时变量的疑惑与理解 为什么写这篇文章? 我在学习C过程中,发现C在发生隐式转换时或者出现未命名的变量如字符串再或者在求值的时候,会出现C临时变量(系统自动生成),而这个临时…

PgSQL根据身份证号查询年龄

1、需求:数据库中有身份证号码,也有年龄字段,但是年龄字段不会自动更新,现在需要返回最新的年龄数据。 2、思路:获取当前年份,截取省份证中的年龄部分数据,再进行相减即可; 3、具体…

MySQL高级学习笔记

1、MySQL架构组成 1.1 高级MySQL介绍 什么是DBA? 数据库管理员,英文是Database Administrator,简称DBA; 百度百科介绍 数据库管理员(简称DBA),是从事管理和维护数据库管理系统(D…

记录新人的web3之旅

简单记录一下自己奇妙又充满热情的web3之旅,希望能勉励未来的自己 2023.10.25—— 第一次觉得对web3,币圈感到好奇是我在油管看了《隐藏的币圈亿万富翁》。这个简短的纪录片讲了郑皓升的传奇A9人生,从币圈中致富,再到被制裁,被软…

web渗透测试漏洞复现:Kibana 未授权访问

web渗透测试漏洞复现 Kibana 未授权访问漏洞复现Kibana系统概述Kibana漏洞概述Kibana漏洞复现Kibana漏洞修复Kibana 未授权访问漏洞复现 Kibana系统概述 Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elastics…

搜索测试题题解(3月21号总结)

目录 1.Shufflem Up 2.Pots 3.Open the Lock 1.Shufflem Up 样例 InputcopyOutputcopy 2 4 AHAH HAHA HHAAAAHH 3 CDE CDE EEDDCC 1 2 2 -1 题意:本题要求将s1和s2合并,再将合并的s分为s1和s2,知道s为我们需要得到的期望s,输…

巨细!Python爬虫详解

爬虫(又称为网页蜘蛛,网络机器人,在 FOAF 社区中间,更经常的称为网页追逐者);它是一种按照一定的规则,自动地抓取网络信息的程序或者脚本。 如果我们把互联网比作一张大的蜘蛛网,那…

北航最新!基于条纹投影的半透明物体3D重建方法

作者:小柠檬 | 来源:3DCV 在公众号「3DCV」后台,回复「原论文」可获取论文pdf 添加微信:dddvision,备注:3D高斯,拉你入群。文末附行业细分群 详细内容请关注3DCV 3D视觉精品课程:…

雷池 WAF 社区版:下一代 Web 应用防火墙的革新

黑客的挑战 智能语义分析算法: 黑客们常利用复杂技术进行攻击,但雷池社区版的智能语义分析算法能深入解析攻击本质,即使是最复杂的攻击手法也难以逃脱。 0day攻击防御: 传统防火墙难以防御未知攻击,但雷池社区版能有效…

Haiku python 库

Haiku 是一个由 DeepMind 开发的 Python 库,用于构建神经网络模型。它旨在提供一种简单、可组合和可维护的方式来定义神经网络结构,同时保持与 JAX(Just Another eXperiment)深度学习库的兼容性。 以下是 Haiku 库的一些主要特点…

01_Kubernetes基础

Kubernetes为什么叫K8S:因为K和S之间有8个字母 为什么需要K8S 对于云计算来说有自己的交互标准 Paas的下一代标准就是容器化,容器的集群化有没有很好的方案?有需求就会有产品,这个产品就叫做资源管理器。 首先是Apache的MESOS&…

LeetCode每日一题【206. 反转链表】

思路:双指针,一前一后,逐个把指向后面的指针指向前面。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), ne…

刷题训练之滑动窗口

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c,Python等 > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:熟练掌握滑动窗口算法,并且能把下面的…

node 常用命令

Node.js的常用命令包括多种类型,从运行JavaScript文件到管理Node.js的模块和包。以下是一些主要的Node.js常用命令: 运行JavaScript文件: node filename.js 这个命令会调用Node.js程序来运行指定的JavaScript文件。 查看文件和目录&#xf…

又一个城市火了,媒介盒子盘点城市爆火原因

近日,“甘肃天水麻辣烫”在各大平台频频登上热搜榜,甘肃当地也及时接住了这泼天富贵,开通“麻辣烫专线”、机场高铁免费接、免费送门票等。这些措施似曾相识,因为在天水前,已经有淄博和哈尔滨这两个城市的案例可以供天…

每日一练:LeeCode-153. 寻找旋转排序数组中的最小值【二分法+最值】

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到: 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]若旋转 7 次&#xff0…

【Linux】实现进度条小程序

个人主页 : zxctscl 如有转载请先通知 文章目录 1. 前言2. 回车和换行3. 缓冲区4. 进度条4.1 倒计时设置4.2 进度条4.2.1 实现简单进度条4.2.2 进度条完善 5. 附进度条代码5.1 Processbar.h5.2 Processbar.c5.3 Main.c5.4 Makefile 1. 前言 在之前已经了解了 【Lin…