java多线程那点事
屌丝程序员们对自己的技术能力总是毫不掩饰的高调,更有甚者每当完成一个简单的功能或算法实现,恨不得从工位上跳起来,生怕谁不知道一样,心情能理解,但个人完全鄙视这种行为。说到底,大家日常的coding,大多在单线程下执行,代码书写的顺序即执行的顺序,很多时候也是我们解决问题的逻辑顺序。有很多代码,如果考虑多线程,从并发的角度去实现,伪“大牛”们可能就要原形毕露了,很多同学更是束手无策。那么,多线程真的那么可怕么?接下来本人 把自己的一些理解分享出来,如有不当,欢迎指正。
java多线程问题简单一点说就是同一时间内有多个java线程对同一份内存数据进行存取。如果业务逻辑上对该份数据的值变化过程有要求,那么多线程的执行顺序不确定性将打乱该要求从而引起问题.因此,搞明白以下三个事情,也就能有效的理解并规避上述问题。
1.线程如何对内存中的变量进行存取?
2.线程究竟以何种顺序执行?
3.多线程如何控制同步?
大家知道,由于计算机的存储设备与处理器的运算速度之间有着数量级之间的差距,因此java采用高速cache作为中间桥梁,在缓存一致性协议下与主内存同步,如下图(java内存模型):
java内存模型规定所有的变量都存储在共享内存中,每条线程都有属于自己的工作内存,该工作内存中保存了该线程使用到的变量的共享内存副本拷贝,线程在工作期间对变量的所有存取操作都只是针对此副本拷贝,而不是真正共享内存中的变量,不同的线程之间无法直接访问对方工作内存中的变量,线程执行完操作后,同步协议将副本拷贝与共享内存对应变量进行同步,由于虚拟机对线程的抢占式调度,当多个线程同时同步一份数据的时候,必然出现并发问题。因此,解决并发首先要了解此同步协议,简单点说,该同步协议主要有以下八种操作:
(1) lock:将共享内存中的变量锁定,为一个线程所独占
(2) unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量
(3) read:将共享内存中的变量值读到工作内存当中
(4) load:将read读取的值保存到工作内存中的变量副本中。
(5) use:将值传递给线程的代码执行引擎
(6) assign:将执行引擎处理返回的值重新赋值给变量副本
(7) store:将变量副本的值存储到共享内存中。
(8) write:将store存储的值写入到共享内存的共享变量当中。
我们可以看到,要保证数据的同步,lock和unlock定义了一个线程访问一次共享内存的界限,有lock操作也必须有unlock操作,另外一些操作也必须要成对出现才可以,像是read和load、store和write需要成对出现,如果单一指令出现,那么就会造成数据不一致的问题。Java内存模型也针对这些操作指定了必须满足的规则:
(1) read和load、store和write必须要成对出现,不允许单一的操作,否则会造成从主内存读取的值,工作内存不接受或者工作内存发起的写入操作而主内存无法接受的现象。
(2) 在线程中使用了assign操作改变了变量副本,那么就必须把这个副本通过store-write同步回主内存中。如果线程中没有发生assign操作,那么也不允许使用store-write同步到主内存。
(3) 在对一个变量实行use和store操作之前,必须实行过load和assign操作。
(4) 变量在同一时刻只允许一个线程对其进行lock,有多少次lock操作,就必须有多少次unlock操作。在lock操作之后会清空此变量在工作内存中原先的副本,需要再次从主内存read-load新的值。在执行unlock操作前,需要把改变的副本同步回主存。
明白了java线程对变量的存取后,接下来需要知道的是线程究竟是以何种顺序执行,可以简单总结为一句话:”如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的“。即在线程内,指令的顺序表现为串行,在线程之间,指令表现为重排序以及工作内存与主内存的同步延迟。线程的执行有以下一些天然的顺序规则,粗略的说,如果两个操作之间的顺序关系不在以下规则里,并且也无法从以下规则中推导出,那么就可以断定它们是没有顺序性保障的,其行为不是线程安全的。
1.程序次序规则:在一个线程内,按照程序代码控制流(非书写顺序流,因为有分支,循环等),书写在前面的操作先行发生于后面的操作。这条规则保证了我们在日常的单线程环境下,能用业务逻辑的顺序去处理代码编写的顺序。当然,很多伪大牛们也在这条规则下coding的风生水起。不过此处仍有一个注意点,那就是JVM的优化有时候会导致指令重排,即后面的代码有可能先被处理器执行,比如 int a = 0; int b =1; 有可能执行器先给b赋值,然后给a赋值。这并不违反此规则,因为重排从线程内根本无法感知,无伤大雅,只是JVM做的一个优化。
2.管程锁定规则:对同一个锁,unlock操作先行发生于后面的lock操作。
3.volatile变量规则:对volatile修饰的变量,写操作先行发生于读操作。这也是大多数情况下我们使用volatile保证多线程操作变量的可见性的原因。
4.线程启动规则:Thread对象的start方法先行发生于此线程的每一个工作。这是必须
的。有点类似先取媳妇再生娃,未婚先育是不允许的。
5.线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。
6.线程的中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。有点类似先有鸡,然后由鸡粪。
7.对象终结规则:一个对象的初始化完成先行发生于它的finalize方法的开始。有点类似先有男人,然后有人妖。
8.先行传递规则:如果操作A先行发生于B,操作B先行发生于C,那么操作A先行发生于C。
上面的规则是无须使用任何同步手段保障就会发生的,在单线程下,重点参考规则1,一般来说无须考虑过多的顺序问题,但在多线程下,从上面的规则可以看到,无论是对变量的存取还是对象方法的执行,都很难套用上述规则,因此大部分都是线程不安全的。
既然多线程下不安全,那如何控制同步以促使其安全呢,这也是我们需要回答的第三个问题。一般我们需要做好以下几点:
1.尽量将对象放在单线程里进行存取,其他线程不参与修改,即所谓的线程封闭。如局部变量,final修饰的不可变对象,ThreadLocal类封装等。
2.使用volatile修饰变量使其多线程可见,使用synchronized同步方法或代码块
3.使用线程安全的容器替代非线程安全的容器,如Hashtable,synchronizedMap,ConcurrentMap,CopyOnWriteArrayList,ConcurrentLinkedQueue等。
4.使用java.util.concurrent.atomic下多种类小工具包,使读取-更新-保存过程原子化。
5.使用同步工具类进行互斥同步,主要包括以下几种类:
a:闭锁CountDownLatch,类似游戏关卡,只用把预先设定的所有关卡都countDown()后,await()才会解除阻塞,继续执行。
b:FutureTask.get,任何完成后此方法才返回结果,未完成前处于阻塞状态。
c:信号量Semaphore,类似公共厕所的蹲位,调用acquire()占用一个蹲位后开始LS,完事后调用release()释放蹲位提裤子走人,当蹲位满时,调用acquire()想用蹲位的人必须 憋着等待有人释放,如果一直人没有释放,就一直憋着或超时拉在裤子上。多用于池化实现,如连接池。
d:栅栏Barrier,类似旅游团包车,人数一定,每个上车的人调用await等待其他的人上车,当所有旅客都上车后,最后一个上车的司机开动车子,驶向预定的目标。
当然多线程还需要考虑很多的事情,以上阐述也也只是很小很浅显的一部分,水平有限,再咧咧恐贻笑大方了。简单理解多线程同步,就是利用同步手段使多个线程按一定顺序存取变量,无非也就涉及到如何操作存取,如何执行存取,如何调度存取,因此明白了本文最开始提出的三个问题,相信大家在今后的多线程并发中不会感到茫然,起码头脑中会有基本的思路,也可以帮伪大牛们真正走向成为大牛的道路,当然多线程远不止这些,真正成为大牛还得不断的摸索实践,也欢迎大家多分享。
java多线程那点事的更多相关文章
- 关于JAVA多线程的那些事__初心者
前言 其实事情的经过也许会复杂了点,这事还得从两个月前开始说.那天,我果断不干IT支援.那天,我立志要做一个真正的程序猿.那天,我26岁11个月.那天,我开始看Android.那天,我一边叨念着有朋自 ...
- Java多线程编程那些事:volatile解惑--转
http://www.infoq.com/cn/articles/java-multi-thread-volatile/ 1. 前言 volatile关键字可能是Java开发人员“熟悉而又陌生”的一个 ...
- Java多线程(二)关于多线程的CPU密集型和IO密集型这件事
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- java从基础知识(十)java多线程(下)
首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...
- Java多线程系列--“JUC集合”03之 CopyOnWriteArraySet
概要 本章是JUC系列中的CopyOnWriteArraySet篇.接下来,会先对CopyOnWriteArraySet进行基本介绍,然后再说明它的原理,接着通过代码去分析,最后通过示例更进一步的了解 ...
- Java多线程编程详解
转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...
- 【转】 Java 多线程之一
转自 Java 多线程 并发编程 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进 ...
- Java多线程编程总结(精华)
Java多线程编程总结 2007-05-17 11:21:59 标签:多线程 java 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http ...
- java多线程并发编程与CPU时钟分配小议
我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现 ...
随机推荐
- Eclipse创建javaWeb项目工程
首先,想作为一个较为标准的程序员,应该把自己的开发工具eclipse设置成与公司中大多数程序员的设置一样,比如说工作的字符编码为UTF-8,字体大小等等. 在刚下载好的eclipse中刚进去是没有To ...
- 解剖Nginx·自动脚本篇(7)类型相关脚本系列
1 auto/types/sizeof 该脚本的功能,是通过测试程序获知给定的ngx_type的大小. 1.1 显示提示信息 echo $ngx_n "checking for $ngx_t ...
- Python 2.7 爬取51job 全国java岗位
一页有50条数据一共2000页 分页是get分页 #!/usr/bin/python # encoding: utf-8 import requests import threading from ...
- java-tip-Collections.synchronized系列生成的容器
这个系列的容器,和Vector或者HashTable之流的差不多, 区别是: Vector和HashTable是在关键方法上加synchronized关键字 而 Collections.synchro ...
- 钉钉开发笔记(六)使用Google浏览器做真机页面调试
注: 参考文献:https://developers.google.com/web/ 部分字段为翻译文献,水平有限,如有错误敬请指正 步骤1: 从Windows,Mac或Linux计算机远程调试And ...
- Python binascii
Python binascii模块 Python binascii模块 用处 包含的函数 相关内置函数 code使用示例 转载请标明出处(http://blog.csdn.net/lis_12/art ...
- ICO流程,casestudy
https://medium.com/crypto-oracle/ico-analysis-framework-nex-case-study-bf65586b4b32
- 对max_allowed_packet这个参数的误解
之前一篇文章知识对,这个参数一个泛泛的概念,但是并没有理解这个参数的真正意义,现在差不多有那么点儿感觉了,主要的意思是每一条记录是一个包,不可拆分,而且包括blob,text等大字段.
- [C++] Const Summary (mind map)
Const Summary
- 洛谷 P3112 [USACO14DEC]后卫马克Guard Mark
题目描述 Farmer John and his herd are playing frisbee. Bessie throws the frisbee down the field, but it' ...