java+向前进一_Java 线程基础

前言

线程并发系列文章:

熟练掌握线程原理与使用是程序员进阶的必经之路,网上很多关于Java线程的知识,比如多线程之间变量的可见性、操作的原子性,进而扩展出的Volatile、锁(CAS/Synchronized/Lock)、信号量等知识。有些文章只说笼统的概念、有些文章深入底层源码令人迷失其中、有些文章只说了其中某个点没有提及内在的联系。

基于以上原因,本系列文章尝试由浅入深、系统性地分析、总结Java线程相关知识,算是加深印象、夯实基础,也算是抛砖引玉。若是相关文章对各位看官有所帮助,幸甚至哉。

通过本篇文章,你将了解到:

1、进程与线程区别

2、开启/停止线程

3、线程的交互

1、进程与线程区别

程序与进程

平时所说的编写一个程序/软件,比如编写好一个APK,这个APK可以直接传送给另一个设备安装,这时候我们说发送给你一个程序/软件,是个静态的单个文件/多个文件的集合。

当安装好APK之后,运行该APK,该程序就被CPU执行了,这时候我们称这个进程在运行了。因此进程是程序的动态表现,也是CPU执行时间段的描述。

ddd34fb0c8be

image.png

当然,程序与进程也不是一一对应关系,也就是说一个程序里可以fork()多个进程来执行任务。

进程与线程

CPU调度执行程序之前,需要准备好一些数据,如程序所在的内存区域,程序需要访问的外设资源等,程序运行过程中产生的一些中间变量需要临时存储在寄存器等。这些与进程本身关联的东西称之为进程上下文。

由此引发的问题:CPU在切换进程的过程中势必涉及到上下文的切换,切换的过程会占用CPU时间。

ddd34fb0c8be

image.png

通俗点理解就是:进程1先被CPU调度执行,执行了一段时间后调度进程2执行,此时上下文就会切换成与进程2相关的。

再考虑另一种情形:一个程序里实现了A、B两个有关联的功能,两者在不同的进程实现,A进程需要与B进程交互,该过程就是个IPC(进程间通信)。我们知道,IPC需要共享内存或者陷入内核调用,这些操作代价比较大。

Android 进程间通信系列文章请移步:Android IPC 看了都懂系列

随着计算机硬件越来越强大,CPU频率越来越高,甚至还发展出多个CPU。为了充分利用CPU,线程应运而生。

进程被分为更小的粒度,原本一个进程要执行A、B、C三个任务,现在将这三个任务分别放在三个线程里执行。

ddd34fb0c8be

image.png

可以看出,CPU调度的基本单位就是线程。

进程与线程关系

1、进程与线程均是CPU执行时间段的描述。

2、进程是资源分配的基本单位,线程是CPU调度的基本单位。

3、一个进程里至少有一个线程。

4、同一进程里的各个线程可以共享变量,它们之间的通信称之为线程间通信。

5、线程可以看作粒度更小的进程。

线程的优势

1、开启新线程远比开启新进程节约资源,并且更快速。

2、线程间通信比IPC简单、快捷易于理解。

3、符合POSIX规范的线程可以跨平台移植。

2、开启/停止线程

既然线程如此重要,那么来看看Java中如何开启与停止线程。

开启线程

查看Thread.java源码可知,Thread实现了Runnable接口,因此需要重写Runnable方法:run()。

#Thread.java

@Override

public void run() {

if (target != null) {

target.run();

}

}

而线程开启后执行任务的方法即是run()。

该方法里先判断target是否不为空,若是则执行target.run()。

#Thread.java

/* What will be run. */

private Runnable target;

target为Runnable类型,该引用可以通过Thread构造方法赋值。

由此看就比较明显了,要线程实现任务,要么直接重写run()方法,要么传入Runnable引用。

继承Thread

声明MyThread继承自Thread,并重写run()方法

static class MyThread extends Thread {

@Override

public void run() {

System.out.println("thread running by extends...");

}

}

private static void startThreadByExtends() {

MyThread t2 = new MyThread();

t2.start();

}

生成Thread引用后,调用start()方法开启线程。

实现Runnable

先构造Runnable,再将Runnable引用传递给Thread。

private static void startThreadByImplements() {

Runnable runnable = new Runnable() {

@Override

public void run() {

System.out.println("thread running by implements...");

}

};

Thread t1 = new Thread(runnable);

t1.start();

}

生成Thread引用后,调用start()方法开启线程。

停止线程

线程开启后,被CPU调度后执行run()方法,该方法执行完毕线程正常退出。当然也可以在run()方法执行途中退出该方法(设置标记位,满足条件即退出),该线程也将停止。若是run()方法里正在Thread.sleep(xx)、Object.wait()等方法,可以使用interrupt()方法中断线程。

private static void stopThread() {

MyThread t2 = new MyThread();

t2.start();

//中断线程

t2.interrupt();

//已废弃

t2.stop();

}

3、线程的交互

硬件层面

先来看看CPU和主存的交互:

ddd34fb0c8be

image.png

CPU运算速度远远高于访问主存的速度,也就是说,当CPU需要计算如下表达式:

int a = a + 1;

首先从主存里拿到a的值,访问主存的过程中CPU是等待状态,当从主存拿到a的值后才进行运算。这个过程显然很浪费CPU的时间,因此在主存与CPU之间增加了高速缓存,顾名思义,当拿到a的值后,放到高速缓存,下次再次访问a的时候先去看看缓存里是否有,有的话直接拿到放到寄存器里,最后按照一定的规则将改变后的a的值刷新到主存里。

访问速度:寄存器-->高速缓存-->主存,CPU在寻找值的时候先找寄存器,再到高速缓存,最后到主存。

你可能已经发现问题了,如下代码:

int a = 1;

int a++;

线程A、线程B分别执行上述代码,假设线程A被CPU1调度,线程B被CPU2调度。线程A、B分别执行a = 1,此时CPU1、CPU2的高速缓存分别存放着a=1,当线程A执行a++时发现高速缓存有值于是直接拿出来计算,结果是:a=2。

当线程B执行时同样的从高速缓存获取值来计算,结果是:a=2。

最后高速缓存将修改后的值回写的主存,结果是a=2。

这样的结果不是我们愿意看到的,CPU针对此种情况设计了一套同步高速缓存+主存的机制:MESI(缓存一致性协议)

该协议约定了各个CPU的高速缓存间与主存的配合,尽量保证缓存数据是一致的。但是由于StoreBuffer/InvalidateQueue的存在,还需要配合Volatile使用。

有关Volatile详细解析请移步:真正理解Java Volatile的妙用

软件层面

由于寄存器、高速缓存的存在,让我们有种感觉:每个线程都拥有自己的本地内存。

实际上,JVM设计了JMM(Java Memory Model Java内存模型):

ddd34fb0c8be

image.png

本地内存是个虚拟概念,如下代码:

static Integer integer = new Integer(0);

public static void main(String args[]) {

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

integer = 5;

}

});

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

integer = 6;

}

});

t1.start();

t2.start();

}

integer 在主存中只有一份,可能还存在于寄存器、高速缓存等地方,这些地方对应的是本地内存。而不是每个线程又重新复制了一份数据。

再看看一段代码:

static boolean flag = false;

static int a = 0;

public static void main(String args[]) {

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

a = 1; //1

flag = true; //2

}

});

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

if (flag) { //3

a = 2; //4

}

}

});

t1.start();

t2.start();

}

若是线程1先执行完,线程2再执行,结果没问题。若是两个线程同时执行,由于//1 //2之间没有依赖关系,编译器/处理器 可能会对//1 //2交换位置,这就是指令重排。如此之后,有可能执行顺序是:2->3->4->1,还有可能是其它顺序,最终的结果是不可控的。

线程交互的核心

从上述的软件层面、硬件层面分析可知,线程1、线程2、线程3各自的本地内存对其它线程是不可见的;多个线程写入主存时可能会存在脏数据;指令重排导致结果不可控。

多线程交互需要解决上述三个问题,这三个问题也是线程并发的核心:

1、可见性

2、原子性

3、有序性

上述三者既是并发核心,也是基础,只有满足了三者,线程并发的共享变量结果才是可控的。

我们熟知的锁、Volatile等是针对三者中的某个或者全部提出的解决方案。

互斥与同步

互斥的由来

要满足并发的三个条件,想想该怎么做呢?

先来看看原子性,既然多线程同时访问共享变量容易出问题,那么想到的是大家排队来访问它,当其中一个线程(A)在访问时,其它线程不能访问,并排队等待A线程执行完毕后,等待中的线程再次尝试访问共享变量,我们把操作共享变量的代码所在的区域称为临界区,共享变量称为临界资源。

//临界区

{

a = 5;

b = 6;

c = a;

}

如上面的代码,多个线程不能同时访问临界区。

这种访问方式称为:互斥。

也就是说多个线程互斥地访问临界区可以实现操作的原子性。

同步的由来

临界区内的操作的共享变量在不同的线程可能有不一样的处理,如下代码:

//伪代码

int a = 0;

//线程1执行

private void add() {

while(true) {

if (a < 10)

a++;

}

}

//线程2执行

private void sub() {

while(true) {

if (a > 0)

a--;

}

}

线程1、线程2都对变量a进行了操作,两者都依赖a的值做一些操作。

线程1判断如果a<10,则a需要自增;线程2判断如果a>0,则a需要自减。

线程1、线程2分别不断地去检查a的值看是否满足条件再做进一步操作,这么做没问题,但是效率太低。如果线程1、线程2检查到不满足条件先停下来等待,当满足条件时由对方通知自己,这样子就不用傻乎乎地每次跑去问a是多少了,极大提升了效率。

因此,交互变成这样子:

//伪代码

int a = 0;

//线程1执行

private void add() {

while(true) {

if (a < 10)

a++;

else

//等待,并通知线程2

}

}

//线程2执行

private void sub() {

while(true) {

if (a > 0)

a--;

else

//等待,并通知线程1

}

}

这么说流程有点枯燥,我们用个小比喻类比一下:

用小明表示线程1、小刚表示线程2,小明要发一批集装箱,先把箱子拿到库房外的空地上,空地面积有限,最多只能放10个箱子,等待小刚过来拿货。

1、刚开始小刚发现空地没货,于是等待小明通知。小明发现没货,开始放货。

2、小明发现空地上还可以放箱子,于是继续放。

3、小明发现箱子已经放了10个,空地占满了,于是就休息下来不再放了,并打电话告诉小刚,我的货够了,你快点过来拿货吧。

4、小刚收到通知后,过来拿货,一直拿,当发现货拿完之后,就不再拿了,并打电话告诉小明,货拿完了,你快放货吧。

于是整个流程简述:小明放了10个箱子就等待小刚拿,小刚拿完之后通知小明继续放。值得注意的是:上述是批量放了箱子,再批量拿箱子,并没有拿一个放一个。关于这个问题,后面细说

又因为小明、小刚都依赖于箱子的个数做事,通过上面对互斥的分析,我们知道需要将这部分操作包裹在临界区里进行互斥访问。

我们把上面的交互过程称之为:同步

同步与互斥关系

可以看出,同步是在互斥的基础上增加了等待-通知机制,实现了对互斥资源的有序访问,因此同步本身已经实现了互斥。

同步是种复杂的互斥

互斥是种特殊的同步

解释了互斥、同步概念,那么该这么实现呢?

接下来系列文章将重点分析系统提供的机制是如何实现可见性、原子性、有序性的以及互斥、同步与三者的关系。

下篇文章:聊聊Unsafe的作用及其用法。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android

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

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

相关文章

电子报账系统源码_网上商城系统建设心得,轻松搞定选择困难

当前&#xff0c;我们正处于一个互联网飞速发展的时代&#xff0c;特别是互联网电商的出现&#xff0c;给我们的生活带来了翻天覆地的变化&#xff0c;不出家门便可购买各种商品&#xff0c;不用再到处奔走寻找&#xff0c;通过网络便可快速下单&#xff0c;然后坐等送货上门。…

源码安装httpd

1 tar -xvf apr-1.7.0.tar.gz tar -xvf apr-util-1.6.1.tar.gz tar -xvf httpd-2.2.6.tar.gz 2cd apr-1.7.0 ./configure Make Make install 3cd apr-util-1.6.1 ./configure --with-apr/usr/local/apr 解决rpm -ivh libexpat-devel-2.2.4-alt1.i586.rpm tar -xvf expat-2.2…

python如何运行py程序_如何用Python汇款:Web3.py教程

Python开发人员对于以太坊和区块链库的技术练习。警告&#xff1a;下面的教程包含这些元素&#xff1a;显式加密、点对点金融服务以及可能的违规行为。这些例子仅仅是为了说明Python区块链的强大功能和便捷性。嗨&#xff0c;Pythoners&#xff0c;你们好&#xff01;我真的很喜…

基于域名的apache服务器

1承接上个博客说的&#xff0c;咱们继续扩展 Cd /usr/local/apache2/conf /usr/local/apache2/conf/extra扩展文件 Vi httpd-vhosts.conf <VirtualHost *:80> ServerAdmin 1327629137qq.com DocomentRoot “/data/webapps/www1” ServerName www.wugk1.com <Directory…

MySQL 数据库修改登录密码

MySQL 数据库修改登录密码、、 -------- mysql修改密码 默认的密码为空&#xff1a;mysql -u root -p第一次更改密码&#xff1a;mysqladmin -uroot -p password xhyEnter password: xhy 第二次更改密码&#xff1a; mysqladmin -uroot -pxhy password xhy1mysql -u root -p En…

python列表统计每个元素出现次数_python 统计list中各个元素出现的次数的几种方法...

利用字典dict来完成统计举例&#xff1a;a [1, 2, 3, 1, 1, 2]dict {}for key in a:dict[key] dict.get(key, 0) 1print dict输出结果&#xff1a;>>>{1: 3, 2: 2, 3: 1}利用Python的collection包下Counter的类举例&#xff1a;from collections import Countera …

raid5需要几块硬盘_Raid5磁盘阵列数据恢复思路分析--附真实案例

1.raid5磁盘阵列数据恢复思路分析Raid5磁盘阵列是一种相对安全的磁盘阵列形式&#xff0c;数据分布状态有点类似于raid0磁盘阵列。但是raid5阵列比raid0阵列更为安全的一点就是阵列的每一组平行数据块中都包含了一个校验块&#xff0c;校验块的作用主要表现在阵列有一块硬盘掉线…

Linux思维导图之sed、实战习题

命令解释&#xff1a; ◆sed 2p /etc/passwd第二行打印了两次其余一次 ◆sed-n 2p /etc/passwd 只打印出第二行 ◆sed-n 1,4p /etc/passwd 只打印出1到4行 ◆sed-n /root/p /etc/passwd只打印出root的行 ◆ sed-n 2./root/p /etc/passwd打印从2行开始往下到root行 ◆sed-n /^$/…

jenkins安装(1)

1先在互联网上输入jenkins.io 2下载jenkins.war 3上传到服务器上 安装jdk前面的博客已经说过了 4 Jenkins requires Java versions [8, 11] but you are running with Java 1.7 from /usr/java/jdk1.7.0_06/jre java.lang.UnsupportedClassVersionError: 51.0 at Main.verifyJa…

jenkins安装(用户配置)(2)

1安装rebulider&#xff08;再次构建可以少写很多参数&#xff09;插件 2安装safe restart安全重启 3系统管理—Configure Global Security—安全矩阵 添加admin用户给与全部权限 4系统管理—管理用户—添加用户 给与用户所有权限&#xff0c;取消第一个勾选&#xff0c;不给予…

发生系统错误53_SAP那些事-推理剧-36-奇怪的付款清账(F-53)报错“TABLE_INVALID_INDEX”...

问题描述&#xff1a;在使用F-53进行供应商付款清账操作时&#xff0c;模拟凭证&#xff08;包括保存凭证&#xff09;时出现如下的ABAP Down错误&#xff1a;问题分析&#xff1a;从报错内容看&#xff0c;我们首先看到报错的程序为SAPMF05A&#xff0c;这个程序财务顾问都熟悉…

微信公众平台-杂项:小程序导航

ylbtech-微信公众平台-杂项&#xff1a;小程序导航1.返回顶部 1、小程序导航 微导航 http://www.we123.com/xcx/ 91udhttp://www.91ud.com/app/ 微信主页 http://www.weixinzhuye.com/app.html 2、2.返回顶部3.返回顶部4.返回顶部5.返回顶部 6.返回顶部1、公众号导航 微小宝 ht…

jenkins安装环境搭建(3)

1安装环境搭建 yum -y install java安装java环境 2安装git用于存储和管理源代码 yum -y install git 3安装并配置git git config --global user.name “yao666” git config --global user.email 1327629137qq.com ssh-keygen -t rsa -C 1327629137qq.com cd ~/.ssh 证书正确 …

seo伪原创工具_文章伪原创工具哪个好用(伪原创工具有哪些)

从事网站seo优化的工作基本上每天都会和文章打交道&#xff0c;因为网站的排名与网站的收录关系是非常大的&#xff0c;网站的收录又和文章息息相关&#xff0c;搜索引擎的胃口是比较喜欢新的内容、原创的内容&#xff0c;而对于一些文案功底比较没那么好的SEOer来说&#xff0…

Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析【转】...

Android系统的运行时库层代码是用C来编写的&#xff0c;用C 来写代码最容易出错的地方就是指针了&#xff0c;一旦使用不当&#xff0c;轻则造成内存泄漏&#xff0c;重则造成系统崩溃。不过系统为我们提供了智能指针&#xff0c;避免出现上述问题&#xff0c;本文将系统地分析…

函数调用关系图如何画_彩铅画入门植物教程 | 如何用彩铅画一株多肉?多肉彩铅画教程步骤图详细...

画画不难&#xff0c;难的是不拿起手中的笔去画。彩铅画入门植物教程 | 如何用彩铅画一株多肉&#xff1f;多肉彩铅画教程步骤图详细多肉的质感如何表达呢&#xff1f;还是那句话&#xff1a;艺术来源于生活&#xff0c;要仔细观察。拿我们今天画的多肉来说&#xff0c;首先要观…

jenkins安装 新建节点(5)

1部署程序&#xff0c;tomcat部署不多说 2新建节点 系统管理—管理节点—新建节点 3完成 4这一点要注意 进行验证任务 创建任务—构建一个自由风格的软件—确定 限制项目的运行节点 选择testenv 构建 执行脚本 输入ifconfig验证 构建成功

工厂方法模式_1天1个设计模式——工厂方法模式

意图工厂方法模式是一种创建型设计模式&#xff0c; 其在父类中提供一个创建对象的方法&#xff0c; 允许子类决定实例化对象的类型。问题假设你正在开发一款物流管理应用。1.0版本只能支持处理卡车运输&#xff0c;因此大部分的代码都位于名为Truck的类中。随着业务越来越广泛…

hadoop简介(大数据技术)

Hadoop可运行于一般的商用服务器上&#xff0c;具有高容错、高可靠性、高扩展性等特点 特别适合写一次&#xff0c;读多次的场景 适合 大规模数据 流式数据&#xff08;写一次&#xff0c;读多次&#xff09; 商用硬件&#xff08;一般硬件&#xff09; 不适合 低延时的数据访问…

hive简介(大数据技术)

背景和概念 Hive是一个依赖于分布式存储的查询和管理大型数据集的数据仓库。传统的非大数据行业一般都是基于表进行数据存储和管理的&#xff0c;如果由于业务扩张或者其他原因迁移到HDFS平台上&#xff0c;那么需要将传统的SQL查询语句全部翻译成Map-reduce的程序实现&#xf…