Java数据库中的锁机制制有什么用

博客分类:
提高系统并发吞吐能力是构建高性能服务的重点和难点。通常review代码时看到synchronized是我都会想一想,这个地方可不可以优化。使用synchronized使得并发的线程变成顺序执行,对系统并发吞吐能力有极大影响,我的博文
介绍了可以从理论上估算系统并发处理能力的方法。
那么对于必须使用synchronized的业务场景,这里提供几个小技巧,帮助大家减小锁粒度,提高系统并发能力。
初级技巧 - 乐观锁
乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。
以下面的代码为例,悲观锁的实现:
public Object get(Object key) {
synchronized(map) {
if(map.get(key) == null) {
// set some values
return map.get(key);
乐观锁的实现:
public Object get(Object key) {
Object val =
if((val = map.get(key) == null) {
// 当map取值为null时再加锁判断
synchronized(map) {
if(val = map.get(key) == null) {
// set some value to map...
return map.get(key);
中级技巧 - String.intern()
乐观锁不能很好解决大量写冲突问题,但是如果很多场景下,锁实际上只是针对某个用户或者某个订单。比如一个用户必须先创建session,才能进行后面的操作。但是由于网络原因,创建用户session的请求和后续请求几乎同时达到,而并行线程可能会先处理后续请求。一般情况,需要对用户sessionMap加锁,比如上面的乐观锁。在这种场景下,可以讲锁限定到用户本身上,即从原来的
lock.lock();
int num=storage.get(key);
storage.set(key,num+1);
lock.unlock();
lock.lock(key);
int num=storage.get(key);
storage.set(key,num+1);
lock.unlock(key);
这个比较类似于数据库表锁和行锁的概念,显然行锁的并发能力比表锁高很多。
使用String.inter()是这种思路的一种具体实现。类 String 维护一个字符串池。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。可见,当String相同时,String.intern()总是返回同一个对象,因此就实现了对同一用户加锁。由于锁的粒度局限于具体用户,使系统获得了最大程度的并发。
public void doSomeThing(String uid) {
synchronized(uid.intern()) {
CopyOnWriteMap?
既然说到了“类似于数据库中的行锁的概念”,就不得不提一下MVCC,Java中CopyOnWrite类实现了MVCC。Copy On Write是这样一种机制。当我们读取共享数据的时候,直接读取,不需要同步。当我们修改数据的时候,我们就把当前数据Copy一份副本,然后在这个副本 上进行修改,完成之后,再用修改后的副本,替换掉原来的数据。这种方法就叫做Copy On Write。
但是,,,JDK并没有提供CopyOnWriteMap,为什么?下面有个很好的回答,那就是已经有了ConcurrentHashMap,为什么还需要CopyOnWriteMap?
Fredrik Bromee 写道
I guess this depends on your use case, but why would you need a CopyOnWriteMap when you already have a ConcurrentHashMap?For a plain lookup table with many readers and only one or few updates it is a good fit.Compared to a copy on write collection:Read concurrency:Equal to a copy on write collection. Several readers can retrieve elements from the map concurrently in a lock-free fashion.Write concurrency:Better concurrency than the copy on write collections that basically serialize updates (one update at a time). Using a concurrent hash map you have a good chance of doing several updates concurrently. If your hash keys are evenly distributed.If you do want to have the effect of a copy on write map, you can always initialize a ConcurrentHashMap with a concurrency level of 1.
高级技巧 - 类ConcurrentHashMap
String.inter()的缺陷是类 String 维护一个字符串池是放在JVM perm区的,如果用户数特别多,导致放入字符串池的String不可控,有可能导致OOM错误或者过多的Full GC。怎么样能控制锁的个数,同时减小粒度锁呢?直接使用Java ConcurrentHashMap?或者你想加入自己更精细的控制?那么可以借鉴ConcurrentHashMap的方式,将需要加锁的对象分为多个bucket,每个bucket加一个锁,伪代码如下:
Map locks = new Map();
List lockKeys = new List();
for(int number : 1 - 10000) {
Object lockKey = new Object();
lockKeys.add(lockKey);
locks.put(lockKey, new Object());
public void doSomeThing(String uid) {
Object lockKey = lockKeys.get(uid.hash() % lockKeys.size());
Object lock = locks.get(lockKey);
synchronized(lock) {
// do something
关于高性能缓存的设计,请参考构建高性能服务系列之一:
浏览 18305
论坛回复 /
(4 / 6237)
浏览: 244628 次
来自: 北京
RingBuffer不存在生产覆盖未消费的数据,或者消费已经消 ...
完全没看懂诶
楼主 为什么是1.27F 求解释
有个疑问,请教下:扩容问题之一:如果不降低命中率?如果使用为垂 ...
maoyidao 写道Yiyang说的对,不过这只是一个理论上 ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'Java锁机制
时间: 17:49:22
&&&& 阅读:162
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&JAVA并发笔记:
JDK发展史:
JDK1.0:提供了一个纯解释的Java虚拟机实现
JDK1.3:把Java技术体系拆分为3个方向,J2SE,J2EE,J2ME,并且Java虚拟机第一次内置了JIT
JDK1.4:增加正则表达式,异常链,NIO,日志类,XML解析器和XSLT转换器等
JDK1.5:自动装箱,泛型,动态注解,枚举,可变长参数,遍历循环等,在虚拟机和API层面上,这个版本改进了Java的内存模型JMM,提供了java.util.concurrent并发包的部分
JDK1.6:对Java虚拟机内部做了大量改进,包括锁与同步,垃圾收集,类加载等方面的算法
JDK1.7:提供新的G1收集器
1.线程的优点:可以降低开发和维护的开销,并且能够提高复杂应用的技能。线程通过把的工作流程转化为普遍存在的顺序流程,使程序模拟人类工作和交互变得更容易。
线程在GUI应用程序中是非常有用的,可用来改变用户接口的响应性,在服务器应用中,用于提高资源的利用率和吞吐量。如:JVM中的垃圾收集器即依赖于多线程。
缺点:可能带来安全性问题
线程安全定义:当多个线程访问同一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步在调用方式代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。
总结:线程安全的类封装了任何必要的同步,因此客户不需要自己提供。
无状态对象永远是线程安全的:不包含域也没有引用其他类的域。一次特定计算的瞬时状态,会唯一地存在本地变量中,这些本地变量存储在线程的栈中,只有执行线程才能访问。因为两个线程不共享状态,所以访问用一个服务的其他线程计算结果互不干扰。如:多数servlet都可以实现无状态,极大降低了servlet线程安全的负担。
count++执行过程:读-改-写,可以利用已有的线程安全类(AtomicLong)管理计数器状态。
servlet的service方法如果声明为synchronized,那么每次只能有一个线程执行它。这就违背了servlet初衷——同时可处理多个请求。
2.多处理器:多线程程序通过更有效的利用空闲处理器资源,来提高吞吐量。
3.编写线程安全的代码,本质上就是管理对状态的访问,而且通常是共享的,可变的状态。如:HashMap的状态一部分存储到对象本身中,但同时也存储到很多Map.Entry对象中
4.锁一个synchronized块有两个部分:①锁对象的引用②锁保护的代码块
每个Java对象都可以隐式的扮演一个用于同步的锁的角色:这些内置的锁被称为内部锁或监视锁。
执行线程进入synchronized块之前会自动获得锁;而无论通过正常控制路径退出,还是从块中抛出异常,线程都在放弃对synchronized块的控制时自动释放锁。获得内部锁的唯一途径:进入这个内部锁保护的同步块或方法。
锁不仅仅是关于同步和互斥的,也是关于内存可见的。为了保证所有线程都能看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
重进入:当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞。然后内部锁是可重进入的。意味着请求是基于【每线程】,而不是基于【每调用】的。重进入的实现是通过为每个锁关联一个请求计数和一个占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数置为1。如果同一线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。知道计数器达到0时,锁被释放。
synchronized不仅仅用于原子操作围着划定“临界区”,还有另外一个重要微妙的作用:内存可见性。
当然我们可以使用显式的同步,或内置于类库中的同步机制,来保证对象的安全发布。
可见性:除了锁之外,Java提供了一种同步的弱形式——volatie变量。它确保一个变量的更新以可预见的方式告知其他线程。当一个域声明为volatile类型后,编译器与运行时会监控这个变量,它是共享的,而且对它的操作不会与其他的内存操作仪器被重排序。volatile变量不会缓存在寄存器或者缓存在对其他处理器隐藏的地方。所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。这使得volatile变量相对于synchronized而言,只是轻量级的同步机制。
volatile的语义不足以使自增操作count++原子化(并不能保证原子性),除非你能保证只有一个线程对变量执行写操作。
使用volatile变量条件:
①写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值;
②变量不需要与其他的状态变量共同参与不变约束;
③而且,访问变量时,没有其他原因需要加锁;
重排序:Java允许重排序,所以执行不一定按顺序,但是执行结果是相同的。在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料。在没有进行适当同步的同步的多线程程序中,尝试腿短那些必然发生在内存中的动作时,你总会判断错误。
?5.ThreadLocal:允许将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝,所以get总是返回由当前执行线程通过set的最新值。
6.并发容器CurrentHashMap:
7.写入时复制CopyOnWriteArrayList:是同步List的一个并发替代品,提供了更好的并发性并避免了在迭代期间对容器加锁和赋值。在每次徐亚修改时,他们会创建并重新发布一个新的容器拷贝,以此来实现可变现。
8.可携带结果的任务Callable和Future
Executor框架使用Runnable作为其任务的基本表达形式。但是Runnable只是个相当有限的抽象,虽然能产生一些边界效应(记日志等)但是run不能反悔一个值或者抛出受检查的异常。
9.任务取消与关闭
在Java中,没有哪一种用来停止线程的方式是绝对安全的,因此没有哪一种方式优先用来停止任务。这里只有悬着相互协作的机制,通过协作,使任务和带啊遵循一个统一的协议,用来请求取消
10.避免活跃度危险
安全性和会阅读通常相互牵制。我们使用锁来保证线程安全,但是滥用锁可能引起锁书序死锁。类似的,我们使用线程池和信号量来约束资源的使用,但是却不能知晓那些管辖范围内的活动可能形成资源死锁。
Java应用程序不能从死锁中恢复。
死锁是遇到最常见的活跃度危险,其他还包括饥饿,丢失信号,活锁。
饥饿:当线程访问它所需要的资源却被永久拒绝,以至于不能再继续运行,这样就发生了饥饿。最常见的引发饥饿的资源是CPU周期
活锁:是线程中活跃度失败的另一种形式,尽管没有被阻塞,线程却仍然不能继续,因为它不断重试相同的操作,却总是失败。活锁通常发生在消息处理应用程序中,如果消息处理失败的话,其中传递消息的底层架构会回退整个事务,并把它置回队首。
减少锁的竞争:
我们看到串行化会损害可伸缩性,上下文切换回损害性能。竞争性的锁会同时导致这两种所示,所以减少锁的竞争讷讷狗狗改进性能和可伸缩性。
访问独占锁守护的资源是串行的——一次只能有一个行程访问它。当然,我们有很好的理由使用锁。
并发程序中,对可伸缩性首要的威胁是独占的资源锁。有两个原因影响着锁的竞争性:锁被请求的频率,以及每次持有该所的时间。如果这两者的乘积足够小,那么大多数请求所得尝试都是非竞争的,这样竞争性的锁不会成为可伸缩性巨大的阻碍。但是如果这个锁的请求量很大,线程将会阻塞以等待锁;在极端的情况下处理器将会闲置,即使仍有大量工作等着完成。
有3种方式来减少锁的竞争:
1??减少持有锁的时间(比如可以把与锁无关的代码移出synchronized块)
2??减少请求所得频率
3??或者用协调机制取代独占锁,从而允许更强的并发性
减少锁的粒度,可以通过分拆锁和分离锁来实现。
独占锁的替代方法:
用于减轻竞争锁带来的影响的第三种技术是提前使用独占锁,这有助于使用更友好的并发方式进行共享状态的管理。
ReadWriteLock:可读,写需要或者独占锁。
11.多线程的利弊
优点:使用线程最主要的原因是提高性能,使用线程可以使程序更加充分地发挥出闲置的处理能力,从而更好的利用资源;
缺点:使用多线程总会引入一些性能的开销,包括:与协调线程相关的开销(加锁、信号、内存同步)增加上下文切换,线程的创建和消亡,以及调度的开销。
12.内存同步
性能的开销有几个来源。synchronized和volatile提供的可见性保证要求使用一个特殊的,名为存储关卡的指令,来刷新缓存,使缓存无效,刷新硬件的写缓存,并延迟执行的传递。存储关卡可能同样会对性能产生影响,因为它们抑制了其他编译器的优化;在存储关卡中,大多数操作是不能被冲排序的。
非竞争的同步可以有JVM完全掌控,而竞争的同步可能需要OS的活动,这回增大开销。当锁为竞争性的时候,失败的线程(一个或多个)必然发生则色。JV,既能自旋等待(不断尝试获取锁直到成功),或者在操作系统中挂起(suspending)这个被阻塞的线程。
哪一个效率更高,取决于上下文切换的开销,以及成功地获取锁需要等待的时间这两者之间的关系。自旋等待更适合短期的等待,而挂起适合长时间等待。
14.比较Map的性能
Map&String, ServerConfig& serverMap = new HashMap&String, ServerConfig&
Map&String, ServerConfig& serverMap = new ConcurrentHashMap&String, ServerConfig&
/blog/2020352
网上有人说concurrenthashmap可以取代掉hashmap
在java5.0之前,用于调节共享对象访问的机制只有synchronized和volatile。Java5.0提供了新的选择:ReetrantLock。
ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性的保证。获得ReentrantLock的锁与进入synchronized块有着相同的内存语义,释放ReentrantLock锁与退出synchronized块有相同的内存语义。ReentrantLock提供了与synchronized一样的可重入加锁的语义。ReentrantLock支持Lock接口电一的所有获取锁的模式。与synchronized相比,ReentrantLock为处理不可用的锁提供了更多灵活性。
为什么要创建与内部锁如此相似的机制呢?内部锁在大部分情况下都能很好的工作,但是有一些功能上的局限——不能中断那些正在获取锁的线程,并且在请求所失败的情况下,必须无线等待。内部锁必须在获取他们的代码块中被释放;这很好的简化了代码,与异常处理机制能够进行良好的互动,但是在某些情况下,一个更灵活地加锁机制提供了更好的活跃度和性能。
但是忘记finally释放是一个定时炸弹,这就是ReentrantLock不能完全替代
1.tomcat隔断时间挂的原因?
tomcat class内存太小了,后面陆续加载后挂了,然后重新清理又好了又可以上了
用top看是不是负载过高
dump文件,分析原因
2.打印jvm了日志
debug configuration -verbose:gc -verbose:class -Xloggc:E:\gc2.log
windows-preferences-java-Installed JREs 编辑JRE -XX:+PrintGCDetails
3.私有锁/内部锁
JDK发展史:
JDK1.0:提供了一个纯解释的Java虚拟机实现
JDK1.3:把Java技术体系拆分为3个方向,J2SE,J2EE,J2ME,并且Java虚拟机第一次内置了JIT
JDK1.4:增加正则表达式,异常链,NIO,日志类,XML解析器和XSLT转换器等
JDK1.5:自动装箱,泛型,动态注解,枚举,可变长参数,遍历循环等,在虚拟机和API层面上,这个版本改进了Java的内存模型JMM,提供了java.util.concurrent并发包的部分
JDK1.6:对Java虚拟机内部做了大量改进,包括锁与同步,垃圾收集,类加载等方面的算法
JDK1.7:提供新的G1收集器
1.线程的优点:可以降低开发和维护的开销,并且能够提高复杂应用的技能。线程通过把的工作流程转化为普遍存在的顺序流程,使程序模拟人类工作和交互变得更容易。
线程在GUI应用程序中是非常有用的,可用来改变用户接口的响应性,在服务器应用中,用于提高资源的利用率和吞吐量。如:JVM中的垃圾收集器即依赖于多线程。
缺点:可能带来安全性问题
线程安全定义:当多个线程访问同一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步在调用方式代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。
总结:线程安全的类封装了任何必要的同步,因此客户不需要自己提供。
&&&&&& 这个问题很经典,无数面试官想从这个问题得到你对线程和进程的理解程度,想完全讲清楚有点儿难度,同时也需要篇幅。从程序开发角度来讲,进程是资源分配的基本单位,是一个程序或者服务的基本单位。我们可以说进程就是程序的执行过程,这个过程包括很多东西,如CPU执行时间、运行内存、数据等,而且是一个动态的过程。线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。总结一下二者关系:
a&.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
b&.资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
c&.处理机分给线程,即真正在处理机上运行的是线程。
d&.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
无状态对象永远是线程安全的:不包含域也没有引用其他类的域。一次特定计算的瞬时状态,会唯一地存在本地变量中,这些本地变量存储在线程的栈中,只有执行线程才能访问。因为两个线程不共享状态,所以访问用一个服务的其他线程计算结果互不干扰。如:多数servlet都可以实现无状态,极大降低了servlet线程安全的负担。
count++执行过程:读-改-写,可以利用已有的线程安全类(AtomicLong)管理计数器状态。
servlet的service方法如果声明为synchronized,那么每次只能有一个线程执行它。这就违背了servlet初衷——同时可处理多个请求。
2.多处理器:多线程程序通过更有效的利用空闲处理器资源,来提高吞吐量。
3.编写线程安全的代码,本质上就是管理对状态的访问,而且通常是共享的,可变的状态。如:HashMap的状态一部分存储到对象本身中,但同时也存储到很多Map.Entry对象中
4.锁一个synchronized块有两个部分:①锁对象的引用②锁保护的代码块
每个Java对象都可以隐式的扮演一个用于同步的锁的角色:这些内置的锁被称为内部锁或监视锁。
执行线程进入synchronized块之前会自动获得锁;而无论通过正常控制路径退出,还是从块中抛出异常,线程都在放弃对synchronized块的控制时自动释放锁。获得内部锁的唯一途径:进入这个内部锁保护的同步块或方法。
锁不仅仅是关于同步和互斥的,也是关于内存可见的。为了保证所有线程都能看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
重进入:当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞。然后内部锁是可重进入的。意味着请求是基于【每线程】,而不是基于【每调用】的。重进入的实现是通过为每个锁关联一个请求计数和一个占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数置为1。如果同一线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。知道计数器达到0时,锁被释放。
synchronized不仅仅用于原子操作围着划定“临界区”,还有另外一个重要微妙的作用:内存可见性。
当然我们可以使用显式的同步,或内置于类库中的同步机制,来保证对象的安全发布。
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
分几种情况:
&&&&&1.其他方法前是否加了synchronized关键字,如果没加,则能。
&&&&&2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
&&&&&3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
&&&&&4.如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。 &
可见性:除了锁之外,Java提供了一种同步的弱形式——volatie变量。它确保一个变量的更新以可预见的方式告知其他线程。当一个域声明为volatile类型后,编译器与运行时会监控这个变量,它是共享的,而且对它的操作不会与其他的内存操作仪器被重排序。volatile变量不会缓存在寄存器或者缓存在对其他处理器隐藏的地方。所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。这使得volatile变量相对于synchronized而言,只是轻量级的同步机制。
volatile的语义不足以使自增操作count++原子化(并不能保证原子性),除非你能保证只有一个线程对变量执行写操作。
使用volatile变量条件:
①写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值;
②变量不需要与其他的状态变量共同参与不变约束;
③而且,访问变量时,没有其他原因需要加锁;
重排序:Java允许重排序,所以执行不一定按顺序,但是执行结果是相同的。在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料。在没有进行适当同步的同步的多线程程序中,尝试腿短那些必然发生在内存中的动作时,你总会判断错误。
这里有必要讲一下volatile的作用,在使用到的时候能明白下面两条即可:
·&&&&&&&& 保证变量对所有线程是可见的。
·&&&&&&&& 禁止优化。
5.ThreadLocal:允许将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝,所以get总是返回由当前执行线程通过set的最新值。
6.并发容器CurrentHashMap:
7.写入时复制CopyOnWriteArrayList:是同步List的一个并发替代品,提供了更好的并发性并避免了在迭代期间对容器加锁和赋值。在每次徐亚修改时,他们会创建并重新发布一个新的容器拷贝,以此来实现可变现。
8.可携带结果的任务Callable和Future
Executor框架使用Runnable作为其任务的基本表达形式。但是Runnable只是个相当有限的抽象,虽然能产生一些边界效应(记日志等)但是run不能反悔一个值或者抛出受检查的异常。
9.任务取消与关闭
在Java中,没有哪一种用来停止线程的方式是绝对安全的,因此没有哪一种方式优先用来停止任务。这里只有悬着相互协作的机制,通过协作,使任务和带啊遵循一个统一的协议,用来请求取消
10.避免活跃度危险
安全性和会阅读通常相互牵制。我们使用锁来保证线程安全,但是滥用锁可能引起锁书序死锁。类似的,我们使用线程池和信号量来约束资源的使用,但是却不能知晓那些管辖范围内的活动可能形成资源死锁。
Java应用程序不能从死锁中恢复。
死锁是遇到最常见的活跃度危险,其他还包括饥饿,丢失信号,活锁。
饥饿:当线程访问它所需要的资源却被永久拒绝,以至于不能再继续运行,这样就发生了饥饿。最常见的引发饥饿的资源是CPU周期
活锁:是线程中活跃度失败的另一种形式,尽管没有被阻塞,线程却仍然不能继续,因为它不断重试相同的操作,却总是失败。活锁通常发生在消息处理应用程序中,如果消息处理失败的话,其中传递消息的底层架构会回退整个事务,并把它置回队首。
减少锁的竞争:
我们看到串行化会损害可伸缩性,上下文切换回损害性能。竞争性的锁会同时导致这两种所示,所以减少锁的竞争讷讷狗狗改进性能和可伸缩性。
访问独占锁守护的资源是串行的——一次只能有一个行程访问它。当然,我们有很好的理由使用锁。
并发程序中,对可伸缩性首要的威胁是独占的资源锁。有两个原因影响着锁的竞争性:锁被请求的频率,以及每次持有该所的时间。如果这两者的乘积足够小,那么大多数请求所得尝试都是非竞争的,这样竞争性的锁不会成为可伸缩性巨大的阻碍。但是如果这个锁的请求量很大,线程将会阻塞以等待锁;在极端的情况下处理器将会闲置,即使仍有大量工作等着完成。
有3种方式来减少锁的竞争:
1??减少持有锁的时间(比如可以把与锁无关的代码移出synchronized块)
2??减少请求所得频率
3??或者用协调机制取代独占锁,从而允许更强的并发性
减少锁的粒度,可以通过分拆锁和分离锁来实现。
独占锁的替代方法:
用于减轻竞争锁带来的影响的第三种技术是提前使用独占锁,这有助于使用更友好的并发方式进行共享状态的管理。
ReadWriteLock:可读,写需要或者独占锁。
11.多线程的利弊
优点:使用线程最主要的原因是提高性能,使用线程可以使程序更加充分地发挥出闲置的处理能力,从而更好的利用资源;
缺点:使用多线程总会引入一些性能的开销,包括:与协调线程相关的开销(加锁、信号、内存同步)增加上下文切换,线程的创建和消亡,以及调度的开销。
12.内存同步
性能的开销有几个来源。synchronized和volatile提供的可见性保证要求使用一个特殊的,名为存储关卡的指令,来刷新缓存,使缓存无效,刷新硬件的写缓存,并延迟执行的传递。存储关卡可能同样会对性能产生影响,因为它们抑制了其他编译器的优化;在存储关卡中,大多数操作是不能被冲排序的。
非竞争的同步可以有JVM完全掌控,而竞争的同步可能需要OS的活动,这回增大开销。当锁为竞争性的时候,失败的线程(一个或多个)必然发生则色。JV,既能自旋等待(不断尝试获取锁直到成功),或者在操作系统中挂起(suspending)这个被阻塞的线程。
哪一个效率更高,取决于上下文切换的开销,以及成功地获取锁需要等待的时间这两者之间的关系。自旋等待更适合短期的等待,而挂起适合长时间等待。
14.比较Map的性能
Map&String, ServerConfig& serverMap = new HashMap&String, ServerConfig&
Map&String, ServerConfig& serverMap = new ConcurrentHashMap&String, ServerConfig&
网上有人说concurrenthashmap可以取代掉hashmap
currentHashMap和HashMap的区别
 (1)ConcurrentHashMap对整个桶数组进行了分段,而HashMap则没有
 (2)ConcurrentHashMap在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而HashMap没有锁机制,不是线程安全的&
hashmap本质数据加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,通过数组下标取出链表,遍历链表用equals取出对应key的value。从数组(通过hash值)取得链表头,然后通过equals比较key,如果相同,就覆盖老的值,并返回老的值。(该key在hashmap中已存在)否则新增一个entry,返回null。新增的元素为链表头,以前相同数组位置的挂在后面。新增后,如果发现size大于threshold了,就resize到原来的2倍。新建一个数组,并将原来数据转移过去。
有3个关键参数:
capacity:容量,就是数组大小
loadFactor:比例,用于扩容
threshold:=capacity*loadFactor
最多容纳的Entry数,如果当前元素个数多于这个就要扩容(capacity扩大为原来的2倍)
ConcurrentHashMap:实现细节/topic/344876
Java 5中支持高并发、高吞吐量的线程安全HashMap实现
在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁得几率,提高并发效率。
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。
在java5.0之前,用于调节共享对象访问的机制只有synchronized和volatile。Java5.0提供了新的选择:ReetrantLock。
ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性的保证。获得ReentrantLock的锁与进入synchronized块有着相同的内存语义,释放ReentrantLock锁与退出synchronized块有相同的内存语义。ReentrantLock提供了与synchronized一样的可重入加锁的语义。ReentrantLock支持Lock接口电一的所有获取锁的模式。与synchronized相比,ReentrantLock为处理不可用的锁提供了更多灵活性。
为什么要创建与内部锁如此相似的机制呢?内部锁在大部分情况下都能很好的工作,但是有一些功能上的局限——不能中断那些正在获取锁的线程,并且在请求所失败的情况下,必须无线等待。内部锁必须在获取他们的代码块中被释放;这很好的简化了代码,与异常处理机制能够进行良好的互动,但是在某些情况下,一个更灵活地加锁机制提供了更好的活跃度和性能。
但是忘记finally释放是一个定时炸弹,这就是ReentrantLock不能完全替代
&Synchronized & ReentrantLock 类
Synchronized:
它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
ReentrantLock 类:
Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
lock 必须在 finally 块中释放,否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
Lock lock = new ReentrantLock();
lock.lock();
// update object state
} finally {
lock.unlock();
与目前的 synchronized 实现相比,争用下的 ReentrantLock 实现更具可伸缩性。(在未来的 JVM 版本中,synchronized 的争用性能很有可能会获得提高。)这意味着当许多线程都在争用同一个锁时,使用 ReentrantLock 的总体开支通常要比 synchronized 少得多。
16.悲观锁 乐观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
17.数据库事务隔离级别
出现上述情况,即我们所说的脏读,两个并发的事务,
“事务A:领导给singo发工资”
“事务B:singo查询工资账户”,
事务B读取了事务A尚未提交的数据。
当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。
两个并发的事务,
“事务A:singo消费”
“事务B:singo的老婆网上转账”
事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。
大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。
Mysql的默认隔离级别就是Repeatable read
Repeatable read避免了不可重复读,事务B不能进行修改,但还有可能出现幻读
Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
18.运行时异常和一般异常
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种党见运行错误。Java编译器要求方法心须声明抛出可能发生的
非运行时异常,但是并不要求必须声明抛出未捕获的运行时异常。
运行时异常:由java虚拟机抛出的异常。用户不必处理。
而一般异常是用户可以抛出的异常,如果抛出调用必须进行处理。
1,从机制角度来讲:
运行时异常:
  在定义方法时不需要声明会抛出
  在调用这个方法时不需要捕获这个
  runtime exception是从java.lang.RuntimeException或java.lang.Error类衍生出来的。
  定义方法时必须声明所有可能会抛出的
  在调用这个方法时,必须捕获它的checked exception,不然就得把它的exception传递下去;
  checked exception是从java.lang.Exception类衍生出来的。
2,从逻辑的角度来说,
运行时异常和一般异常是有不同的使用目的的。一般异常用来指示一种调用方能够直接处理的异常情况。而运行时则用来指示一种调用方本身无法处理或恢复的程序错误。
&& Java提供了两类主要的异常:runtime exception和checked exception。checked异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
&&&&& 但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
&&&&& 如果不想终止,则必须扑捉所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。在这个场景这样处理可能是一个比较好的应用,但并不代表在所有的场景你都应该如此。如果在其它场景,遇到了一些错误,如果退出程序比较好,这时你就可以不太理会运行时异常,或者是通过对异常的处理显式的控制程序退出。
常见的Rntime Exceptio:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
19.java多线程管理 concurrent包用法详解
Executor& && && && && && &&&&&&&& :具体Runnable任务的执行者。
ExecutorService& && && &&&&&&&:一个线程池管理者,其实现类有多种,我会介绍一部分。我们能把&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&
Runnable,Callable提交到池中让其调度。
Semaphore& && && && && &&&&&&&&:一个计数信号量
ReentrantLock& && && && & &&&&:一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。
Future& && && && && && &&&&&&&&&&&& :是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等等,还
提供了cancel终止线程。
BlockingQueue& && && && & &&&& :阻塞队列。
CompletionService& && && &&&& &: ExecutorService的扩展,可以获得线程执行结果的
CountDownLatch& && && && &&& :一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。&
CyclicBarrier& && && && & &&&&&&&& :一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点&
Future& && && && && && &&&&&&&&&&&
& :Future 表示异步计算的结果。
ScheduledExecutorService & :一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
  Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
  然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
  抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
  然后ThreadPoolExecutor继承了类AbstractExecutorService。
  在ThreadPoolExecutor类中有几个非常重要的方法:
&&&&&& execute()
&&&&&& submit()
&&&&&& shutdown()
&&&&&& shutdownNow()
&&&&&& execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
  submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
&&&&&& 比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
&标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!

我要回帖

更多关于 java中的锁机制 的文章

 

随机推荐