java线程一
我们可以在计算机上运行各种计算机软件程序。每一个运行的程序可能包括多个独立运行的线程(Thread)。
线程(Thread)是一份独立运行的程序,有自己专用的运行栈。线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
同步这个词是从英文synchronize(使同时发生)翻译过来的。我也不明白为什么要用这个很容易引起误解的词。既然大家都这么用,咱们也就只好这么将就。
线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
因此,关于线程同步,需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。
关于线程同步,需要牢牢记住的第二点是 “共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
关于线程同步,需要牢牢记住的第三点是,只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
关于线程同步,需要牢牢记住的第四点是:多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。
同步锁
前面讲了为什么要线程同步,下面我们就来看如何才能线程同步。
线程同步的基本实现思路还是比较容易理解的。我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。
生活中,我们也可能会遇到这样的例子。一些超市的外面提供了一些自动储物箱。每个储物箱都有一把锁,一把钥匙。人们可以使用那些带有钥匙的储物箱,把东西放到储物箱里面,把储物箱锁上,然后把钥匙拿走。这样,该储物箱就被锁住了,其他人不能再访问这个储物箱。(当然,真实的储物箱钥匙是可以被人拿走复制的,所以不要把贵重物品放在超市的储物箱里面。于是很多超市都采用了电子密码锁。)
线程同步锁这个模型看起来很直观。但是,还有一个严峻的问题没有解决,这个同步锁应该加在哪里?
当然是加在共享资源上了。反应快的读者一定会抢先回答。
没错,如果可能,我们当然尽量把同步锁加在共享资源上。一些比较完善的共享资源,比如,文件系统,数据库系统等,自身都提供了比较完善的同步锁机制。我们不用另外给这些资源加锁,这些资源自己就有锁。
但是,大部分情况下,我们在代码中访问的共享资源都是比较简单的共享对象。这些对象里面没有地方让我们加锁。
读者可能会提出建议:为什么不在每一个对象内部都增加一个新的区域,专门用来加锁呢?这种设计理论上当然也是可行的。问题在于,线程同步的情况并不是很普遍。如果因为这小概率事件,在所有对象内部都开辟一块锁空间,将会带来极大的空间浪费。得不偿失。
于是,现代的编程语言的设计思路都是把同步锁加在代码段上。确切的说,是把同步锁加在“访问共享资源的代码段”上。这一点一定要记住,同步锁是加在代码段上的。
同步锁加在代码段上,就很好地解决了上述的空间浪费问题。但是却增加了模型的复杂度,也增加了我们的理解难度。
现在我们就来仔细分析“同步锁加在代码段上”的线程同步模型。
首先,我们已经解决了同步锁加在哪里的问题。我们已经确定,同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。
其次,我们要解决的问题是,我们应该在代码段上加什么样的锁。这个问题是重点中的重点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,同步锁本身也一定是多个线程之间的共享对象。
在Java里面,同步锁的概念就是这样的。任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的Object Reference在整个系统运行过程中都保持不变。
一些求知欲强的读者可能想要继续深入了解synchronzied(同步锁)的实际运行机制。Java虚拟机规范中(你可以在google用“JVM Spec”等关键字进行搜索),有对synchronized关键字的详细解释。synchronized会编译成 monitor enter, … monitor exit之类的指令对。Monitor就是实际上的同步锁。每一个Object Reference在概念上都对应一个monitor。
信号量
同步锁模型只是最简单的同步模型。同一时刻,只有一个线程能够运行同步代码。
有的时候,我们希望处理更加复杂的同步模型,比如生产者/消费者模型、读写同步模型等。这种情况下,同步锁模型就不够用了。我们需要一个新的模型。这就是我们要讲述的信号量模型。
信号量模型的工作方式如下:线程在运行的过程中,可以主动停下来,等待某个信号量的通知;这时候,该线程就进入到该信号量的待召(Waiting)队列当中;等到通知之后,再继续运行。
很多语言里面,同步锁都由专门的对象表示,对象名通常叫Monitor。
同样,在很多语言中,信号量通常也有专门的对象名来表示,比如,Mutex,Semphore。
信号量模型要比同步锁模型复杂许多。一些系统中,信号量甚至可以跨进程进行同步。另外一些信号量甚至还有计数功能,能够控制同时运行的线程数。
我们没有必要考虑那么复杂的模型。所有那些复杂的模型,都是最基本的模型衍生出来的。只要掌握了最基本的信号量模型——“等待/通知”模型,复杂模型也就迎刃而解了。
我们还是以Java语言为例。Java语言里面的同步锁和信号量概念都非常模糊,没有专门的对象名词来表示同步锁和信号量,只有两个同步锁相关的关键字——volatile和synchronized。
这种模糊虽然导致概念不清,但同时也避免了Monitor、Mutex、Semphore等名词带来的种种误解。我们不必执着于名词之争,可以专注于理解实际的运行原理。
在Java语言里面,任何一个Object Reference都可以作为同步锁。同样的道理,任何一个Object Reference也可以作为信号量。
Object对象的wait()方法就是等待通知,Object对象的notify()方法就是发出通知。
java线程一的更多相关文章
- java 线程一
java基础学习总结--线程(一) 一.线程的基本概念 线程理解:线程是一个程序里面不同的执行路径 每一个分支都叫做一个线程,main()叫做主分支,也叫主线程. 程只是一个静态的概念,机器上的一个. ...
- 【转】java线上程序排错经验2 - 线程堆栈分析
前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...
- Java线程之 InterruptedException 异常
Java线程之 InterruptedException 异常 当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法. 抛 ...
- Java线上问题排查思路及Linux常用问题分析命令学习
前言 之前线上有过一两次OOM的问题,但是每次定位问题都有点手足无措的感觉,刚好利用星期天,以测试环境为模版来学习一下Linux常用的几个排查问题的命令. 也可以帮助自己在以后的工作中快速的排查线上问 ...
- Arthas - Java 线上问题定位处理的终极利器
前言 在使用 Arthas 之前,当遇到 Java 线上问题时,如 CPU 飙升.负载突高.内存溢出等问题,你需要查命令,查网络,然后 jps.jstack.jmap.jhat.jstat.hprof ...
- Java线上问题排查神器Arthas快速上手与原理浅谈
前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...
- JAVA多线程一
介绍 线程是操作系统的最小单位,一个进程可以创建多个线程. 线程有五种状态,分别是新建.就绪.运行.阻塞.死亡状态. 多线程可以提高执行效率,但是如果单线程可以完成的任务,使用多线程反而会增加不必要的 ...
- java学习多线程之创建多线程一
现在我们有这么一个需求,就是在主线程在运行的同时,我们想做其他的任务,这个时候我们就用到了多线程.那么如何创建多线程,我们知道在系统当中qq的多线程创建是由操作系统来完成的,那么如果我们想在java当 ...
- 深入java多线程一
涉及到 1.线程的启动(start) 2.线程的暂停(suspend()和resume()) 3.线程的停止(interrupt与异常停止,interrupt与睡眠中停止,stop(),return) ...
随机推荐
- python引入模块时import与from ... import的区别(转)
import datetime是引入整个datetime包,如果使用datetime包中的datetime类,需要加上模块名的限定. 1 import datetime 2 3 print datet ...
- 一个团队和他们的调查表-----("调查表与调查结果分析"心得体会)
注:这篇blog主要是描述六小灵童团队在从接到调查表任务到分析调查数据最后完成本次任务的过程,以及过程中的点滴和心德体会.---蔡何 1.制表历程 随着课程的推进,我们逐步进入了软件项目中比较重要的需 ...
- Hibernate分页查询的两个方法
Hibernate中HQL查询语句有一个分页查询, session.setMaxResult(参数)和session.setFirstResult(参数) 如果只设置session.setMaxRes ...
- ALL ANY SOME 这样解释好理解很多
--All:对所有数据都满足条件,整个条件才成立,例如:5大于所有返回的id select * from #A where 5>All(select id from #A) go --Any:只 ...
- android示例:一个简单的登陆程序
最近写了个简单的登陆程序,有几点收获: 1.懂得如何在LinearLayout中嵌套LinearLayout,完善布局的行列: 2.用android:layout_weight控制控件的比重: 3.用 ...
- input上传图片
1.通过input自身的onchange事件触发: <input id="file" type="file" accept="image/*&q ...
- Excel Sheet Column Title (STRING - TYPE CONVERTION)
QUESTION Given a positive integer, return its corresponding column title as appear in an Excel sheet ...
- DOS中命令的格式
---------------siwuxie095 一.DOS中,命令使用格式的一般形式 用中文表达的形式为: [路径] 关键字 [盘符] [路径] 文件名 [扩展名] (参数) [参数 ...
- Nginx配置杂记(转)
转至:http://www.cnblogs.com/kuangke/p/5619400.html Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器,相较 ...
- 过滤输入htmlentities与htmlspecialchars用法
过滤输入 (即来自所列数据源中的任何数据)是指,转义或删除不安全的字符.在数据到达应用的存储层之前,一定要过滤输入数据.这是第一道防线.假如网站的评论表单接收html,默认情况下 访客可以毫无阻拦地在 ...