volatile和synchronized关键字:http://gold.xitu.io/entry/56cfc6d7c24aa8005442deeb/view
线程之间通过内存来通信。
解决缓存不一致性问题:
1)通过在总线加LOCK#锁的方式
问题: 由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。
2)通过缓存一致性协议
一致性实现:
Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
volatile工作原理:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
特点: volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值。声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用。
另外: AtomicInteger线程安全的根源就是valotile
缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。
使用lock实现访问同步:
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
常见使用方式:
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
注意:单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。所以我们在多线程编码时要经常注意当前线程是否被设置了中断状态,如果设置了,我们应该自己去终止线程的运行。
ReentrantLock
可重入锁,是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,它 使得一个线程,外层函数获得锁之后,内层递归函数仍有获得该锁的代码, 可重入锁的最大作用就是 可以避免死锁。
使用方式:
private Lock lock; //注意这个地方
public void insert(Thread thread) {
Lock lock = new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(thread.getName()+"得到了锁");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
}
ReadWriteLock
也是一个接口,在它里面只定义了两个方法:
Lock readLock();
Lock writeLock();
一个用来获取读锁,一个用来获取写锁. ReentrantReadWriteLock实现了ReadWriteLock接口
public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
} finally {
rwl.readLock().unlock();
}
}
如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
总结来说,Lock和synchronized有以下几点不同:
二者都是可重入锁,Lock是公平锁,synchronized是非公平锁,
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;(猜测,synchronized 作为指令由系统层面执行,Lock作为JVM层面有一层包装不会主动释放锁)
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)Lock 有许多封装的功能,通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率(reentrantReadLock)。lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞
6)sychronized是非公平锁,Lock可以设置公平锁
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
原理:
使用两条指令
monitorenter monitorexit
标示线程的进入数,进入加1,出去减一