精选文章 JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、

JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、

作者:老咸驴 时间: 2021-02-05 09:43:22
老咸驴 2021-02-05 09:43:22
【摘要】JUC(java.util.concurrent) .locks.ReenTrantLocks 实现Runnable接口比继承Thread类所具有的优势: 1.适合多个相同的程序代码的线程去共享同一个资源。 2.可以避免java中的单继承的局限性。 3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。 4.线程池只能放入实现Runable或callable类线程,不能...

JUC(java.util.concurrent) .locks.ReenTrantLocks
实现Runnable接口比继承Thread类所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源。
2.可以避免java中的单继承的局限性。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
4.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类


1.同步代码块
Object lock = new Object(); //创建锁
synchronized(1ock){
//可能会产生线程安全问题的代码
}

2.同步方法
//同步方法
public synchronized void method(){
//可能会产生线程安全问题的代码
}


同步方法使用的是this锁
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。 如果两个线程抢票不能实现同步,那么会出现数据错误。


wait()、notify()
wait)、notify(. notifyAl()是三个定义在Object类里的方法 ,可以用来控制线程的状态。
wait:方法会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
notify:方法会通知某个正在等待这个对象的控制权的线程继续运行。
notifyAll方法会通知所有正在等待这个对象的控制权的线程继续运行。
注意: - -定要在线程同步中使用,并且是同一一个锁的资源

wait与sleep区别
●对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
。sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的
时间到了又会自动恢复运行状态。
wait()是把控制权交出去,然后进入等待此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法
后本线程才进入对象锁定池准备获取对象锁进入运行状态。
●在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁。

使用interrupt()方法
使用interrupt()方法来中断程有两种情况:
1)线程处于阻塞状态
如使用了sleep,同步锁的waitsocket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的
interrupt(方法时,会抛出InterruptException异常。 阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然
后break跳出循环状态,从而让我们有机会结束这个线程的执行。
2)线程末处于阻塞状态
使用isInterrupted(判断线程的中断标志来退出循环。当使用interrupt()方法时, 中断标志就会置true,和使用
自定义的标志来控制循环是一样的道理。


线程优先级
PrioritytThread prioritytThread = new PrioritytThread();
现今操作系统基本采用分时的形式调度运行的线程.线程分配得到时间片的多少决定了线程使用处理器资源
的多少,也对应了线程优先级这个概念。
在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。

join()方法
join作用是让其他线程变为等待。thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺
序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。(A.join则A先走)

yield方法
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。( 可能没有效果)
yield()让当前正在运行的线程回到可运行状态.以允许具有相同优先级的其他线程获得运行的机会。因此,使用
yield)的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield)达到让步的目
的,因为,让步的线程可能被线程调度程序再次选中。


多线程并发的3个特性
多线程并发开发中,要知道什么是多线程的原子性,可见性和有序性,以避免相关的问题产生。

1.原子性
原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。
试想一下,如果这2个操作不具备原子性,会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。
这样就会导致账户A虽然减去了 1000元,但是账户B没有收到这个转过来的1000元。
专智指合3048
所以这2个操作必须要具备原子性才能保证不出现一-些意外的问题。

2.可见性
可见性:当多个线程访问同-一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
举个简单的例子,看下面这段代码:
//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;
当线程1执行int 1 = 0这句时, i 的初始值 0 加载到内存中,然后再执行i = 10,那么在内存中 i 的值变为 10了。
如果当线程1执行到int i = 0这句时,此时线程2执行j=i,它读取 i 的值并加载到内存中,注意此时内存当中 i 的值是0 ,那么就会使得j的值也为0,而不是10。
这就是可见性问题,线程 1 对变量 i 修改了之后,线程2没有立即看到线程 1 修改的值。

2.3有序性
有序性:程序执行的顺序按照代码的先后顺序执行
int count = 0;
boolean flag = false;
count = 1; //语句1
flag = true; //语句2
以上代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生——指令重排序( Instruction Reorder )。
什么是重排序?一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致。
as-if-serial:无论如何重排序,程序最终执行结果和代码顺序执行的结果是一致的。 Java编译器、 运行时和处理器都会保证Java在单线程下遵循as-if-serial语意)
上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?
再看下面一个例子:
int a = 10; //语句1
int b = 2; //语句2
a = a+3; //语句3
b = a*a; //语句4
这段代码有4个语句,那么可能的一个执行顺序是:语句2 语句1语句3 语句4
不可能是这个执行顺序:语句2 语句1 语句4 语句3
因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到nstruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。虽然重排序不会影响单个线程内程序执行的结果,但是多线程会有影响。
下面再看一个例子
//线程1:
init = false 
context = loadContext(); //语句1
init = true; //语句2
//线程2:
while(!init){//如果初始化未完成,等待
sleep();

execute(context);//初始化完成。执行逻辑
上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行execute(context)方法,而此时context并没有被初始化,就会导致程序出错。
从上面可以看出,重排序不会影响单个线程的执行,但是会影响到线程井发执行的正确性。

要想并发程序正确的执行,必须保证原子性、可见性及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确


Java内存可见性

了解Java内存模型
JVM内存结构、Java对象模型和Java内存模型,这就是三个截然不同的概念,而这三个概念很容易混淆。这里详细区别-下

1.JVM内存结构
我们都知道, Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若
干个不同的数据区域,这些区域都有各自的用途。其中有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁。
在《Java虚拟机规范(Java SE8)》中描述了JVM运行时内存区域结构如下:


PC寄存器 Java虚拟机栈 本地方法栈


JAVA堆 方法区
运行时常量池

  所有线程共享的数据区域:JAVA堆、方法区
  各个线程独享的数据区域:PC寄存器、Java虚拟机栈、本地方法栈
  运行时常量:运行时常量池


JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能

2.Java对象模型
Java是一种面向对象的语言,而Java对象在JVM中的存储也是有一定的结构的。 而这个关于Java对象自身的存储模型称之为Java对象模型。
HotSpot虚拟机中( Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机) ,设计了一个0OP-Klass Model。O0P ( Ordinary Object Pointer )指的是普通对象指针,而Klass用来描述对象实例的具体类型。
每一个Java类.在被JVM加载的时候. JVM会给这个类创建一个instanceKlass 对象,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中 ,使用new创建一个对象的时候, JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。

3.内存模型
Java内存模型就是一种符合内存模型规范的 ,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果-致的机制及规范。
参考:https://www.hollischuang.com/archives/2550.
Java内存模型是根据英文Java Memory Model ( JMM )翻译过来的。其实JMM并不像JVM内存结构一样是真实存在的。他只是一个抽象的概念。JSR-133: Java Memory Model and Thread Specification中描述了, JMM是和多线程相关的,他描述了一组规则或规范 ,这个规范定义了-一个线程对共享变量的写入时对另一个线程是可见的。
简单总结下, Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、 原子性、顺序性等问题,而MM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一一些语法集 ,这些语法集映射到ava语言中就是volatile、synchronized等关键字。

JMM线程操作内存的基本的规则:
第一条关于线程与主内存:线程对共享变量的所有操作都必须在自己的工作内存(本地内存)中进行.不能直接从主内存中读写
第二条关于线程间本地内存:不同线程之间无法直接访问其他线程本地内存中的变量,线程间变量值的传递需要经过主内存来完成。
●主内存
主要存储的是ava实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一一个变量进行访问可能会发现线程安全问题。
●本地内存
主要存储当前方法的所有本地变量信息(本地内存中存储看主内存中昨变量副本拷贝),每个线程只能访问自己的本地内存,即线程中的本地变量对其它线程是不可见的.就算是两个线程执行的是同一-段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据.线程间无法相互访问工作内存,因此存储在工作内存的数据不
存在线程安全问题。

小结
JVM内存结构,和Java虚拟机的运行时区域有关。
Java对象模型 ,和ava对象在虚拟机中的表现形式有关。
Java内存模型,和Java的并发编程有关。

内存可见性

可见性: 一个线程对共享变量值的修改,能够及时的被其他线程看到
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量
线程A与线程B之间如要通信的话.必须要经历下面2个步骤: 
1.首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2.然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

线程A ————>   线程B

↓↑ ↓↑

本地内存A 本地内存B
x=1 x=1

↓步骤1 ↓步骤2

主内存 x=1


如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时有放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互.来为java程序员提供内存可见性保证。


解决可见性问题
JMM关于synchronized的两条规定:
线程解锁前(退出同步代码块时) :必须把自己工作内存中共享变量的最新值刷新到主内存中
线程加锁时(进入同步代码块时) :将清空本地内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)
做如下修改,在死循环中添加同步代码块
while (flag) {
synchronized (this) {

}

synchronized实现可见性的过程
1.获得互斥锁(同步获取锁)
2.清空本地内存
3.从主内存拷贝变量的最新副本到本地内存
4.执行代码
5.将更改后的共享变量的值刷新到主内存
6.释放互斥锁


同步原理
synchronized的同步可以解决原子性、可见性和有序性的问题,那是如何实现同步的呢?
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1.普通同步方法.锁是当前实例对象this
2.静态同步方法.锁是当前类的class对象
3.同步方法块,锁是括号里面的对象
当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁。
synchronized的同步操作主要是monitorenter和monitorexit这两个jvm指令实现的,先写-段简单的代码 :
public class Demo2Synchronized {
public void test2() {
synchronized (this) {
}
}
}
在cmd命令行执行javaC编译和javap -C Java字节码的指令
javac Demo2Synchronized .java
javap -C Demo2Synchronized.class

可看出同步代码块是使用monitorenter(进入,获取锁)和monitorexit(退出,释放锁)这两个jvm指令实现的


 

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:数据结构之带头结点单链表面试题public int getLength(Node head){ if (head.next == null){ return 0

下一篇:neusport 学院集群docker使用说明

您可能感兴趣

  • CSS清除浮动各种方法

    1,在浮动元素后面增加<br/>标签;   <br/>标签有自带的清除浮动属性; 2,在浮动元素后面增加一个清除浮动层;   <div>     <div style="float:left"></div>     <div style="float:left"></div>     <div style="cle...

  • 浅谈微服务安全架构设计

    微服务现在一直备受关注,很多层出不穷的组件也接踵而来。但是问题来了,微服务的安全性该如何确保呢? 本场 Chat 旨在让大家了解微服务的设计理念,熟悉微服务下各个服务的安全认证该如何确保,并着重讲解 OAuth2 作为 Java 界的鉴权大佬,是如何实现微服务的统一鉴权的。 在本场 Chat 中,会讲到如下内容: 回顾微服务设计理念微服务下的各种安全...

  • 大飞老师带你再看Java线程(二)

    目前java创建线程存在3中方式:1: 通过继承 Thread 类本身;2: 通过实现 Runnable 接口;3: 通过 Callable 和 FutureTask 创建线程 一、通过继承Thread类,并重写run方法实现线程创建 //方式1:继承Thread实现自定义线程 public class MyThread extends Threa...

  • python time 与datetime之间的区别与联系

    一.time模块 time模块提供各种操作时间的函数   一般有两种表示时间的方式:   第一种是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的 #当前时间的时间戳   In [9]: time.time()   Out[9]: 1376102328.536908 第...

  • java 异步上传文件

    我们的java上传文件,需要form同步上传,并且需要设置enctype为multipart/form-data。 如果将form使用ajax异步提交的话,将会报错说enctype不是multipart/form-data类型 但有时候确实又有酱紫的需求,可以实现,需要借助jquery.form.js 插件 插件下载地址...

  • java 解析excle和生成excle文档

    只贴出关键代码,其他一概论之 java解析excle文件: Workbook book = Workbook.getWorkbook(new File(fileName)) ; //filename为需要解析的excle  Sheet sheet = book.getSheet(0);  int rows = ...

  • Java程序员面试中的多线程问题

    2013-3-5 16:32| 发布者: sxwgf| 查看: 1735| 评论: 0|来自: 伯乐在线  http://web.itivy.com/article-184-1.html     很多核心JAVA面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时...

  • 静态代理,JDK代理和CGLIB代理的含义及区别

     一:代理模式(静态代理)           代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理。           静态代理由 业务实现类、业务代理类 两部分组成。业务实现类 负责实现主要的业务方法,业务代理类负责对调用的业务方法作拦截、过滤、预处理,主要是在方法中首先进行预处理动...

CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、介绍:华为云为您免费提供JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、在博客、论坛、帮助中心等栏目的相关文章,同时还可以通过 站内搜索 查询更多JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、的相关内容。| 移动地址: JUC线程安全、wait与sleep区别、interrupt()方法、线程优先级、join()方法、yield()方法、多线程并发的3个特性、JVM内存结构、Java对象模型和Java内存模型、 | 写博客