死锁的产生:
public class DeadLockDemo {
private StringA ="A";
private StringB ="B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
public void deadLock(){
Thread t1 =new Thread(new Runnable() {
@Override
public void run() {
synchronized (A){
try {
Thread.currentThread().sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("1");
synchronized (B){
System.out.println("1");
}
}
}
});
Thread t2 =new Thread(new Runnable() {
@Override
public void run() {
synchronized (B){
try {
Thread.currentThread().sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("2");
synchronized (A){
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
避免死锁的办法:
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里, 否则会出现解锁失败的情况.
资源限制引发的问题.
对于硬件资源限制,可以考虑使用集群并行执行程序.既然单机的资源有限,那么就让程序在多机上运行.使用ODPS/Hadoop 或者自己搭建集群服务器,不同的机器处理不同的数据.
volatile的定义和实现原理
与其相关的CPU术语
1)内存屏障 memory barriers 是一组处理器指令,用于实现对内存操作的顺序限制
2)缓冲行 cashline缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期。
3)原子操作 atomic operations 不可中断的一个或一系列操作
4)缓存行填充cashlinefill当处理器识别到从内存中读取操作数是可以缓存的,处理器读取整个缓存行到适当的缓存(L1,L2,L3的或所有)
5) 缓存命中 cash hit 如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理器从缓存中读取操作数,
而不是从内存中读取
6) 写命中 write hit
7) 写缺失 write misses the cashe
volatile 关键字修饰的共享变量,编译后的汇编代码会多出Lock前缀的指令,指令会在多核处理器下引发两件事
1)将当前处理器缓存行的数据写回到系统内存
Lock前缀指令会引起处理器缓存回写到内存。
2)这个写内存的操作会使在其他CPU里缓存了该内存的数据无效。
MESI控制协议去维护内部缓存和其他处理器缓存的一致性。
synchronized的实现原理与应用
对于普通同步方法, 锁的是当前实例对象。
对于静态同步方法, 锁的是当前类的Class对象。
对于同步方法块,锁的是Synchonized括号里配置的对象。
JVM基于进入和退出Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用Monitorenter和
Monitorexit指令实现的, 而方法的同步是使用另一种方式实现的.
Monitorenter指令在编译后插入到同步代码块的开始位置,而Monitorexit是插入到方法的结束和异常处.JVM要保证每个Monitorenter有一个Monitorexit与之配对.任何对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态.线程执行到
Monitorenter指令时 ,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁.
Java对象头
synchronized用的锁存储在java对象头里.如果对象头是数组,则虚拟机用3个字宽存储对象头,如果非数组,则用2个字宽存储对象头. Java对象头里的Mark Word里默认存储对象的HashCode, 分代年龄和锁标记.
锁的升级
java1.6为了减少获得锁和释放锁带来的性能消耗而引入偏向锁和轻量级锁.锁一共有四种状态:由低到高分别是无锁状态,偏向锁状态,轻量级状态和重量级状态,这几个状态随着竞争情况逐渐升级.锁可以升级,但不能降级.
原子操作实现的原理
原子操作:不可被中断的一个或者一系列操作.
一些术语:
缓存行Cashe line :缓存的最小操作单元.
比较并替换Compare and Swap
CPU流水线CPU pipeline
内存顺序冲突 Memory order voilation
处理器如何实现原子操作.
(1)使用总线锁保证原子性
处理器使用总线锁就是使用处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时, 其他处理器的请求将被阻塞住,那么
该处理器可以独享共享内存.
(2)使用缓存锁保证原子性
在同一时刻, 我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间的通信锁住了,这使得在锁定期间
其他处理器不能操作其他内存地址的数据, 所以总线锁的开销比较大.
缓存锁定:简单说有个两个处理器,每个处理器都有自己的缓存,当内存区域如果被缓存在缓存在处理器的缓存行中,并且在Lock
操作期间被锁定,那么它在执行锁操作回写内存时,处理器不在总线上声明Lock#信号,而是修改内存中内存地址,并允许它的缓存一致性机制保证操作的原子性,因为缓存一致性机制会阻止同时修改两个以上处理器缓存的内存区域数据,当其他处理器回写
已被锁定的缓存行数据时, 会使缓存行无效。
Java中如何实现原子操作
1.使用cas实现原子操作 :JVM中的CAS操作证实利用了处理器提供的CMPXCHG指令实现的。
AtomicBoolean 用原子方式更新boolean值
AtomicInteger 用原子方式更新Integer值
AtomicLong 用原子方式更新long值
在Java并发包中有一些并发框架也使用了CAS的方式来实现原子操作,比如LinkedTransferQueue类的Xferf方法。
基于AtomicInteger 实现线程安全的计数器
private AtomicInteger atomicInteger =new AtomicInteger(0);
private int i =0;
public static void main(String[] args) {
final Counter counter =new Counter();
List ts =new ArrayList(300);
long start = System.currentTimeMillis();
for(int j=0 ; j<100; j++){
Thread t =new Thread(new Runnable() {
@Override
public void run() {
for (int i =0; i<10000;i++ ){
counter.count();
counter.safeCount();
}
}
});
ts.add(t);
}
for (Thread t: ts) {
t.start();
}
//等待所有线程执行完成
for(Thread t:ts){
try {
t.join();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(counter.i);
System.out.println(counter.atomicInteger.get());
System.out.println(System.currentTimeMillis()-start);
}
//使用cas 实现线程安全计数
private void safeCount(){
for(;;){
int i =atomicInteger.get();
boolean suc =atomicInteger.compareAndSet(i,++i);
if (suc){
break;
}
}
}
//非线程安全计数
private void count(){
i++;
}
CAS原子操作的三大问题
1)ABA问题
Java1.5 开始JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先
检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值
设置为给定的新值。
2)循环时间开销大
pause指令
3)只能保证一个共享变量的原子操作
可以用锁保证多个共享变量的原子操作。还有一个取巧方法就是,把多个共享变量合成一个共享变量来操作。从JDK1.5开始
JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里进行CAS操作。











网友评论