汇智动力学院成就你的IT梦!
成都汇智动力老师直接接听
作者:汇智动力学院 来源:汇智动力学院 2022/10/6 14:20:37
上一篇文章中我们提到了Java代码运行的步骤中需要把变量从主...
上图中也看到了,JVM中共定义了8种原子性(下面会讲解原子性)操作来实现主内存和工作内存的交互: read:将主内存中的一个变量的值读取出来 load:将read操作读取的变量值存储到工作内存的副本中 use::把工作内存中的变量的值传递给执行引擎 assign:把从执行引擎中接收的值赋值给工作内存中的变量 store:把工作内存中一个变量的值传递到主内存 write:将store操作传递的值写入到主内存的变量中 lock:将主内存中的一个变量标识为某个线程独占的锁定状态 unlock:将主内存中线程独占的一个变量从锁定状态中释放 通过上述8种原子操作的描述,我们如果要把一个变量从主内存传输到工作内存,那就要执行read和load操作,如果要把一个变量从工作内存写回主内存,就要执行store和write操作。 当然JVM的这几种操作我们是无法直接调用,我们只能通过java中指定好的一些关键字如synchronized等来调用部分操作。 线程操作的三个特性: Java内存模型中想要保证线程并发的安全,那么必须要了解原子性、可见性和有序性线程的这三个特性; 原子性(atomicity) 原子性操作在JVM中是线程安全的,非原子性操作在多线程并发下就会出现线程不安全的问题,这之后就需要我们使用同步技术把非原子性操作变成原子性操作。 JVM中lock和unlock就是把非原子性操作变成原子性操作,JVM中使用的两个字节码指令monitorenter和monitorexit来实现lock和unlock操作,而我们的java代码中则是使用synchronized关键字完成上述的两个操作。 可见性(Visibility) 简单来说就是在多线程操作变量的时候,一个线程修改了变量的值其它线程可以立即知晓这个修改。想要实现可见性需要线程在工作内存中修改了变量值之后立马同步到主内存中刷新主内存中变量的值,线程再次使用变量的时候重新从主内存中读取。这样其他线程在使用的会后就可以使用最新的变量值。 Java中线程的可见性可以通过三个关键字来实现,volatile、synchronized以及final。volatile关键字我们后面使用的会后再说,synchronized保证有序性是因为unlock操作之前必须把变量同步回主内存来实现的;final关键字是因为其修饰的变量在初始化后就会变成不会更改的常量,所以只要在初始化过程中没有把this引用传递出去被外部使用就能保证变量被线程调用的可见性。 有序性(Orderliness) 有序性是指在同一个线程中的所有操作都是有序执行的,但由于指令重排序等行为会导致指令执行的顺序不一定是按照代码中的先后顺序执行的。之前提到过在多线程操作中指令重排会导致线程不安全。在java中可以通过关键字volatile和synchronized保证线程的有序性。Volatile是因为变量被其修饰后指令就不会出现重排保证有序性,而synchronized是因为在变量被lock锁定之后同一时间只能被一个线程使用,即相当于单线程操作,而单线程的指令重排是没有问题的。 重谈指令重排和Happens-Before原则: 指令重排是编译器为了提高指令执行效率,只要程序的最终结果等同于它在严格的顺序化环境中执行的结果,就可以对指令的执行顺序进行重排序,也就是说重排序后指令的执行顺序不一定是代码的顺序。 在单线程下因指令重排的保证执行的结果所以前面指令执行的顺序是否按照代码的顺序不重要;但是在多线程下就会出现问题,例如一个线程A修改了一个变量且还没有来得及写入但是另外一个线程B却进行读取这个变量的时候就会出现问题,这个就是因为线程变量修改不可见和顺序改变引起的了。 上面讲到了volatile和synchronized关键字可以保可见性和有序性,但是Java内存模型中所有的可见性和有序性也不是都要依靠volatile和synchronized来实现,否则不仅会使得我们的一些操作变得非常繁琐,也会大大降低性能(synchronized滥用会对性能影响很大)。 JMM为保证线程操作的可见性和有序性定义了一个两个操作之间的偏序关系,称之为 Happens-Before(先行发生) 原则。比如说操作A先行发生于操作B,那么在B操作发生之前,A操作产生的变量修改等操作都会被操作B可见。 Happens-Before具体如下: 程序顺序规则 一个线程操作中,每个操作都先行发生于它的后续操作。简单来说就是按照代码逻辑发生。 监视器锁规则 一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须注意的是对同一个锁,后面是指时间上的后面。 volatile变量规则 对一个volatile变量的写操作先行发生与后面对这个变量的读操作。 线程启动规则 Thread对象的start()方法先行发生与该线程的每个动作。 线程终止规则 线程中的所有操作都先行发生与对此线程的终止检测,可以通过Thread.join()和Thread.isAlive()的返回值等手段检测线程是否已经终止执行。 线程中断规则 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。 对象finalize规则 一个对象的初始化完成先行发生于他的 finalize 方法 (GC 回收对象调用的方法 ) 的执行。 传递性 如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。 通过上述描述我们就可以知道我们衡量并发安全的问题需要以Happens-Before原则为准,而不是盲目的使用synchronized等关键字来实现并发的安全。 happens-Before中提到的线程的一些方法如:join()、isAlive()、interrupt()等方法,我们会在下篇文字中介绍“线程的操作方法”的时候带大家认识它们,这里就不做过多的解释了。 综上所述 从硬件内存到线程模型到我们JVM内存模型及其线程的各种特性和JMM先行发生规则,我们对于实现线程的并发安全就有了实现思路和理解,这也对我们后面理解实现线程安全的手段奠定了良好的基础。 二、算法的设计原则 ①、正确性:首先,算法应当满足以特定的“规则说明”方式给出的需求。其次,对算法是否“正确”的理解可以有以下四个层次: 一、程序语法错误。 二、程序对于几组输入数据能够得出满足需要的结果。 三、程序对于精心选择的、典型、苛刻切带有刁难性的几组输入数据能够得出满足要求的结果。 四、程序对于一切合法的输入数据都能得到满足要求的结果。 PS:通常以第 三 层意义的正确性作为衡量一个算法是否合格的标准。 ②、可读性:算法为了人的阅读与交流,其次才是计算机执行。因此算法应该易于人的理解;另一方面,晦涩难懂的程序易于隐藏较多的错误而难以调试。 ③、健壮性:当输入的数据非法时,算法应当恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。并且,处理出错的方法不应是中断程序执行,而是应当返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。 ④、高效率与低存储量需求:通常算法效率值得是算法执行时间;存储量是指算法执行过程中所需要的*大存储空间,两者都与问题的规模有关。 前面三点 正确性,可读性和健壮性相信都好理解。对于第四点算法的执行效率和存储量,我们知道比较算法的时候,可能会说“A算法比B算法快两倍”之类的话,但实际上这种说法没有任何意义。因为当数据项个数发生变化时,A算法和B算法的效率比例也会发生变化,比如数据项增加了50%,可能A算法比B算法快三倍,但是如果数据项减少了50%,可能A算法和B算法速度一样。所以描述算法的速度必须要和数据项的个数联系起来。也就是“大O”表示法,它是一种算法复杂度的相对表示方式,这里我简单介绍一下,后面会根据具体的算法来描述。 相对(relative):你只能比较相同的事物。你不能把一个做算数乘法的算法和排序整数列表的算法进行比较。但是,比较2个算法所做的算术操作(一个做乘法,一个做加法)将会告诉你一些有意义的东西; 表示(representation):大O(用它*简单的形式)把算法间的比较简化为了一个单一变量。这个变量的选择基于观察或假设。例如,排序算法之间的对比通常是基于比较操作(比较2个结点来决定这2个结点的相对顺序)。这里面就假设了比较操作的计算开销很大。但是,如果比较操作的计算开销不大,而交换操作的计算开销很大,又会怎么样呢?这就改变了先前的比较方式; 然后我们再说说算法的存储量,包括: 程序本身所占空间; 输入数据所占空间; 辅助变量所占空间; 一个算法的效率越高越好,而存储量是越低越好。 三、算法的分类 算法可以宏泛的分为三类: 一,有限的,确定性算法 这类算法在有限的一段时间内终止。他们可能要花很长时间来执行指定的任务,但仍将在一定的时间内终止。这类算法得出的结果常取决于输入值。 二,有限的,非确定算法 这类算法在有限的时间内终止。然而,对于一个(或一些)给定的数值,算法的结果并不是的或确定的。 三,无限的算法 是那些由于没有定义终止定义条件,或定义的条件无法由输入的数据满足而不终止运行的算法。通常,无限算法的产生是由于未能确定的定义终止条件。 Java中常见的算法有: ①、排序 排序就是对一组数据按照一定的顺序(从大到小或者从小到大)进行排序; 常见排序如下: 简单排序:冒泡排序、选择排序、插入排序; 高级排序:快速排序、希尔排序、归并排序、基数排序、鸡尾酒排序等等; ②、递归 递归是一种直接或者间接调用自身的一种算法,递归的目的是简化程序设计使程序更加易读; ③、查找 在一些(有序的/无序的)数据元素中,通过一定的方法找出与给定关键字相同的数据元素就叫做查找; ④、统计 指对有关数据的搜集、整理、计算、分析、解释、表述等的活动。 往期文章 墙裂推荐 [ 1 ] Java开发之高并发必备篇(二)——线程为什么会不安全? [2] Java开发之高并发必备篇(一)——线程基础 [3] Java开发篇——设计模式(5)装饰设计模式 原创视频 墙裂推荐 2022转行软件测试之校区资讯 ↓↓↓
活动福利 // 1 毕业礼包 | 毕业学员免费赠送《软件测试技术大咖专题课》,助力学员早日突破高薪瓶颈 // 2 入职礼包 |就业学员免费赠送《Java语言开发视频课》及全套源代码,市场价值12800元 // 3 推荐有奖 |推荐好友成功报名,立得丰厚“伯乐”红包 (欢迎详询校区老师)
详询软件测试&开发培训事宜
专业解答各类课程问题、介绍师资和学校情况
微信号:186******73
相关资讯
“成都汇智动力”是成都汇智动力信息技术有限公司在教育宝平台开设的店铺,若该店铺内信息涉嫌虚假或违法,请点击这里向教育宝反馈,我们将及时进行处理。
成都汇智动力·车载测试培训
成都汇智动力·成都软件测试培训
成都汇智动力·IT培训-零基础学IT
成都汇智动力·java开发培训
成都汇智动力·金融测试培训