Java并发编程常见锁及优化
Posted folyh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程常见锁及优化相关的知识,希望对你有一定的参考价值。
六、Java 常见锁
1.乐观、悲观锁
1.1 乐观锁
- 读数据都认为别人不会修改所以不上锁,但是在更新时通过CAS实现
1.2 悲观锁
- 每次读写数据都会上锁进行阻塞
2.自旋锁
2.1 原理
- 如果持有锁的线程能在短时间内释放锁,等待锁的线程只需自旋(不需要进行用户线程和CPU内核切换,进入阻塞挂起而后唤醒[这里需要2个上下文切换过程])
- 等锁释放后就能立即获取锁,避免用户态内核态切换的消耗
- 但是如果一直获取不到锁,就会不断消耗CPU资源,所以需要设置最长等待锁的自旋时间,超过就进入阻塞状态。
2.2 适合场景
- 适合锁竞争不激烈且锁时间短;不适合锁竞争激烈且锁时间长
2.3设置
2.3.1 自旋锁时间阈值
- 1.5固定;1.6引入了适应性自旋锁,根据前一次同一个锁的自旋时间及锁拥有线程的状态决定,默认最佳时间是1个线程上下文切换时间
2.3.2 开启
- 1.6 XX:-UseSpinning;1.7后JVM自旋锁总是会执行,且自动调整自旋次数,无法手动开启关闭。
3.Synchronized、ReentrantLock
3.1 Synchronized
3.1.1 概念
-
关键字,依赖于 JVM,解决多个线程间访问资源同步性问题,保证其修饰的方法或代码块任意时刻只能有一个线程访问
-
可以把任意非NULL的对象当做锁,属于重量级锁、独占式悲观锁、可重入锁、隐式锁、非公平锁
3.1.2 3种主要使用方式
- 修饰实例方法(锁当前对象实例)
- 给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 。
- 修饰静态方法(锁当前类)
- 给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁
- 这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享
- 修饰代码块(锁指定对象/类)
- synchronized(object) 表示进入同步代码块前要获得给定对象的锁。
- synchronized(类.class) 表示进入同步代码前要获得给定 Class的锁
备注:
- 如果线程A调用一个实例对象的静态synchronized方法,而线程B同时调用这个实例对象所属类的非静态synchronized方法是允许的,并不互斥!
- 因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
3.1.3 实现
-
重量级锁:需要调用操作系统相关接口,给线程加锁消耗时间长
-
1.6及之后对锁优化:自适应锁、锁消除、锁粗化、轻量级锁、偏向锁
3.2 ReentrantLock
- 依赖于 JDK的API,继承接口Lock并实现了接口中定义的方法。除能实现synchronized的功能,还提供了可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法,另外还有可选择通知、多个锁。
- 属于轻量级锁、乐观锁、可重入锁、显式锁、实现公平非公平锁(默认非公平锁)
4.公平锁、非公平锁
4.1 公平锁
- 按照锁的请求顺序获取锁
4.2 非公平锁
- 不能保证按照锁的请求顺序获取锁(如JVM随机就近原则分配锁)
- 比公平锁性能高5~10倍,ReentrantLock默认、synchronized都属于
5.可重入锁
- 也称递归锁,同一线程外层函数获得锁之后 ,内层递归函数仍然可以获取该锁,ReentrantLock和synchronized都属于
6.读、写锁
ReadWriteLock、ReentrantReadWriteLock
6.1 读锁
- 读即查询数据使用读锁,多个读锁不互斥,读锁与写锁互斥
6.2 写锁
- 写即修改数据使用写锁,只能有一个人写,且不能同时读取
7.同步锁、死锁
7.1 同步锁
- 同一时间只允许一个线程访问共享数据,synchronized可以获取一个对象的同步锁
2.2 死锁
- 两个或以上线程(或进程)执行过程中,因为争夺资源造成互相等待形成死锁
8.按锁状态分类
8.1 重量级锁
- 通过对象内部的监视器锁monitor实现,而monitor依赖于操作系统Mutex Lock锁实现
- 操作系统实现线程切换需要用户态和和心态切换,时间长
8.2 轻量级锁
- 相对于重量级锁而言。在没有多线程竞争前提下,减少重量级锁产生的消耗
- 适用线程交替执行同步块场景,如果存在同一时间访问同一个锁情况则膨胀为重量级锁
8.3 偏向锁
- 相对于轻量级锁而言。在没有多线程竞争情况下尽量减少不必要的轻量级锁执行路径
- 因为轻量级锁的获取及释放要依赖多次的CAS原子指令,而偏向锁只需要在置换ThreadId的时候依赖一次CAS原子指令
- 适用于只有一个线程执行同步块场景,进一步提高性能。单如果存在多线程竞争的情况则必须撤销偏向锁。
8.4 无锁状态
备注:锁升级
- 锁的竞争造成锁升级,只能单向从低到高
9. 分段锁
- 只对所操作的段加锁,而不影响线程对其他段的访问,如ConcurrentHashMap
10.显式、隐式锁
10.1 显式锁
- 使用者需要手动写代码去获取锁和释放锁,如ReentrantLock
10.2 隐式锁
- 使用者不需要手动写代码去获取锁和释放锁,加锁和释放锁过程也不会显示,如synchronized
六、Java锁优化
1.减少锁持有时间
- 只用在有线程安全要求的程序上加锁,且尽量减少锁持有时间
2.降低锁粒度
- 包括分段锁,将大对象(可能被很多线程访问)拆成小对象,增加并行度,降低锁竞争,增加偏向锁、轻量级锁的占比数量,如ConcurrentHashMap
3.锁分离
- 将功能分离成读锁与写锁,如ReadWriteLock
- 分离思想还可以延伸,如LinkedBlockingQueue根据结构从头部读取数据,从尾部存放数据
4.锁粗化
- 减少锁持有时间的逆向,如果其他线程对该资源的请求频率很低,而该线程需要对该资源频繁访问修改,则可以延长锁持有时间,减少对该锁不停请求、同步、释放操作,进而降低系统资源消耗
链接:
Java锁优化思路及JVM实现
XX、高并发
21.高并发解决方案
1.1 应用程序和静态资源文件分离
同 #### 1.2 页面静态化技术
1.2 页面缓存
可以使用Ngnix提供缓存功能、或者页面缓存服务器Squid
1.3 集群与分布式
1.4 反向代理
1.5 CDN
CDN内容分发网络,是集群页面缓存服务器,尽早返回用户需要的数据,加速用户访问速度,也减轻后端服务器的负载压力;
本篇文章主要参考链接如下:
持续更新中…
随心所往,看见未来。Follow your heart,see light!
欢迎点赞、关注、留言,一起学习、交流!
并发下常见的加锁及锁的PHP具体实现-转载
php并发编程-转自:http://www.cnblogs.com/jingzhishen/p/4328740.html
并发下常见的加锁及锁的PHP具体实现
http://www.cnblogs.com/scotoma/archive/2010/09/26/1836312.html
在最近的项目中有这样的场景
1.生成文件的时候,由于多用户都有权限进行生成,防止并发下,导致生成的结果出现错误,需要对生成的过程进行加锁,只容许一个用户在一个时间内进行操作,这个时候就需要用到锁了,将这个操作过程锁起来.
2.在用了cache的时候,cache失效可能导致瞬间的多数并发请求穿透到数据库此时也可以得需要用锁在同一并发的过程中将这个操作锁定.
针对以上的2种情况,现在的解决方法是对处理过程进行锁机制,通过PHP实现如下
用到了Eaccelerator的内存锁 和 文件锁,原理如下
判断系统中是否安了EAccelerator 如果有则使用内存锁,如果不存在,则进行文件锁
根据带入的key的不同可以实现多个锁直接的并行处理,类似Innodb的行级锁
使用如下:
$lock = new CacheLock(‘key_name‘);
$lock->lock();
//logic here
$lock->unlock();
//使用过程中需要注意下文件锁所在路径需要有写权限.
具体类如下:
<?php /** * CacheLock 进程锁,主要用来进行cache失效时的单进程cache获取,防止过多的SQL请求穿透到数据库 * 用于解决PHP在并发时候的锁控制,通过文件/eaccelerator进行进程间锁定 * 如果没有使用eaccelerator则进行进行文件锁处理,会做对应目录下产生对应粒度的锁 * 使用了eaccelerator则在内存中处理,性能相对较高 * 不同的锁之间并行执行,类似mysql innodb的行级锁 * 本类在sunli的phplock的基础上做了少许修改 http://code.google.com/p/phplock * @author yangxinqi * */ class CacheLock { //文件锁存放路径 private $path = null; //文件句柄 private $fp = null; //锁粒度,设置越大粒度越小 private $hashNum = 100; //cache key private $name; //是否存在eaccelerator标志 private $eAccelerator = false; /** * 构造函数 * 传入锁的存放路径,及cache key的名称,这样可以进行并发 * @param string $path 锁的存放目录,以"/"结尾 * @param string $name cache key */ public function __construct($name,$path=‘lock\\\\‘) { //判断是否存在eAccelerator,这里启用了eAccelerator之后可以进行内存锁提高效率 $this->eAccelerator = function_exists("eaccelerator_lock"); if(!$this->eAccelerator) { $this->path = $path.($this->_mycrc32($name) % $this->hashNum).‘.txt‘; } $this->name = $name; } /** * crc32 * crc32封装 * @param int $string * @return int */ private function _mycrc32($string) { $crc = abs (crc32($string)); if ($crc & 0x80000000) { $crc ^= 0xffffffff; $crc += 1; } return $crc; } /** * 加锁 * Enter description here ... */ public function lock() { //如果无法开启ea内存锁,则开启文件锁 if(!$this->eAccelerator) { //配置目录权限可写 $this->fp = fopen($this->path, ‘w+‘); if($this->fp === false) { return false; } return flock($this->fp, LOCK_EX); }else{ return eaccelerator_lock($this->name); } } /** * 解锁 * Enter description here ... */ public function unlock() { if(!$this->eAccelerator) { if($this->fp !== false) { flock($this->fp, LOCK_UN); clearstatcache(); } //进行关闭 fclose($this->fp); }else{ return eaccelerator_unlock($this->name); } } }
本类在孙立同学的类的基础上做了小点改进的了.具体可以看 http://code.google.com/p/phplock 感谢孙同学的分享精神!
Apache + PHP 的并发访问
文章地址:http://www.cnblogs.com/WestContinent/archive/2013/03/25/2981667.html
1.书写例程:
做成一个测试用的PHP文件代码如下:
<?php //为了测试是否多个用户访问的时候这个值是公用的,即:是否临界资源 $count = 0; //循环十次,消耗十秒,模拟一个费时操作 for ($i=1; $i<=10; $i++,$count++) { echo getTime()." i is $i and count is $count.</br>"; sleep(1); } /** * 获得当前时间,返回字符串,包含毫秒数 * 格式:yyyy-MM-dd HH:mm:ss.fff */ function getTime() { $currentTime = Date(‘Y-m-d H:i:s‘);//Get currentTime str $milisecond = microtime(); $splitmiliTime = explode(‘.‘, $milisecond); $milisecond = $splitmiliTime[1]; $milisecond = substr($milisecond, 0,3); $currentTime = $currentTime.‘.‘.$milisecond; return $currentTime; } ?>
2.把上面做成的文件Copy到Apache的发布目录(一般是Htdocs,但是可以配置)
3.打开两个浏览器同时访问发布PHP页面
分别得到如下结果
浏览器1
2013-03-25 14:55:42.128 i is 1 and count is 0. 2013-03-25 14:55:43.138 i is 2 and count is 1. 2013-03-25 14:55:44.152 i is 3 and count is 2. 2013-03-25 14:55:45.169 i is 4 and count is 3. 2013-03-25 14:55:46.180 i is 5 and count is 4. 2013-03-25 14:55:47.194 i is 6 and count is 5. 2013-03-25 14:55:48.208 i is 7 and count is 6. 2013-03-25 14:55:49.222 i is 8 and count is 7. 2013-03-25 14:55:50.236 i is 9 and count is 8. 2013-03-25 14:55:51.250 i is 10 and count is 9. |
浏览器2
2013-03-25 14:55:41.286 i is 1 and count is 0. 2013-03-25 14:55:42.296 i is 2 and count is 1. 2013-03-25 14:55:43.310 i is 3 and count is 2. 2013-03-25 14:55:44.324 i is 4 and count is 3. 2013-03-25 14:55:45.339 i is 5 and count is 4. 2013-03-25 14:55:46.355 i is 6 and count is 5. 2013-03-25 14:55:47.367 i is 7 and count is 6. 2013-03-25 14:55:48.402 i is 8 and count is 7. 2013-03-25 14:55:49.645 i is 9 and count is 8. 2013-03-25 14:55:50.658 i is 10 and count is 9. |
4.结论
从上面的试验结果可以得到如下结论,因为两组测试数据中的时间犬牙交错,两个用户在耗时操作中并没有出现某一个用户长时间占用执行时间片的情况。说明Apache+Php(loadmodule)是支持多用户并行操作的。另外全局变量Count在两个用户同时访问的时候都是以全新的状态出现的,因此Apache+Php(loadmodule)不支持内存缓存数据,也就是说在多用户并发访问的情况下每次访问都会开辟新的内存。那么如果需要对多用户的操作做同步,只能使用文件锁的方式来实现了。
加锁解锁PHP实现
文章地址:http://blog.csdn.net/topasstem8/article/details/6735240
PHP并没有完善的线程支持,甚至部署到基于线程模型的httpd服务器都会产生一些问题,但即使是多进程模型下的PHP,也难免出现多进程共同访问同一 资源的情况。比如整个程序共享的数据缓存,或者因为资源受限而必须对特定处理过程进行排队,以及针对每个用户生成唯一的某种标识的情形。PHP语言自身没 有提供进程互斥和锁定机制,因而使得在这些情况下的编程遇到了困难,目前了解到的可选的办法有以下这些:
1、利用MySQL的锁定机制来实现互斥。缺点是增大了数据库服务器的连接负担,并且使得程序依赖于数据库服务才能正常工作。
2、利用文件锁机制。也就是利用flock函数通过文件实现锁定和互斥机制,来模拟通用编程模型下的锁定原语的工作方式。这种方式在以前以纯文本文件为存储引擎的时代成为保护数据完整性的必备元素,现在在使用文本文件作为缓存媒介的场合也相当常见。PmWiki应该也是使用了这个机制来对多人同时编辑一个页面的情形进行提醒。不过文件锁机制多少会调用到宿主操作系统上的文件锁特性,因此在使用时一定要检查服务器操作系统是否为PHP环境提供了完善可靠的文件锁机制。
3、利用共享内存空间计数。PHP可以利用shmop_open函数开辟一块内存空间,在服务进程之间共享数据,为了保证共享数据的互斥安全访问,可以使用 sem_get、sem_acquire和sem_release这组函数实现共享计数锁定机制。这种办法在后台实际是调用了系统的ipc 服务来实现。
用 PHP 编写支持高并发的网站,需要做什么处理?
文章地址:http://www.zhihu.com/question/20049768
1、Webserver (Nginx) :这一层是可以轻松分布式部署的,结合智能DNS解析可以简易地防止单点故障、实现区域访问加速,结合LVS很容易实现负载均衡。这一层主要是负责处理静态请求和转发PHP请求至第二层的PHP处理节点,至于静态资源地址(http://misc.xxxx.com)可以单独拿出来部署,或者直接使用商用的云存储服务(国内七牛不错,国外有Amazon S3)
2、PHP 处理节点:一个节点其实就是一个监听特定端口的系统进程,webserver的请求通过负载均衡器(我用的AWS的loadbalancer)进行分发, 很好实现分布式和负载均衡。我现在用的还是php自带的php-fpm,其实facebook出的hhvm性能非常强悍,但是还不能100%通过我项目的 单元测试,等hhvm成熟过后可以平滑替换
3、高速缓存:用的memcached,这一层的作用主要是减轻数据库IO和加快热数据 访问,缓存策略与程序耦合度较高,不赘述,但简单地说有两种方式,一种是在程序的全局层面加一个缓存处理,这种方法代码耦合度低,但是有效命中率不高,有 些项目不一定适应,另一种是在具体的数据存取处加缓存处理,这种办法程序耦合度较高,但是缓存命中率非常高,几乎没有无效缓存存在,我用的是这种。
4、数据库 :我现在的项目数据规模不大,暂时只用了单台数据库,但是程序逻辑上已做好了数据库线性扩展的准备。其实数据库层的扩展是老生常谈了,常用手段是分库分 表,这一块需要在前期的代码就打下基础,另外更平滑地手段是使用中间件,比如360的Atlas,阿里巴巴的cobar,淘宝的TDDL,中间件可以在不 大范围变更代码的情况下扩展,但是具体的使用场景还是有限的,具体项目还需单独考察。
5、其 他:根据不同的项目,架构还可以选择性地使用队列,我现在用的beantalkd,Redis也是一个很好的选择。队列常用的使用环境是邮件发送和站内消息推送上面,但是在某些场景下也可以作为核心数据库的缓冲,对应对大并发或者突发性流量也是不错的选择
以上是关于Java并发编程常见锁及优化的主要内容,如果未能解决你的问题,请参考以下文章