线程模型
数据读取的优先级是:寄存器-->告诉缓存-->内存
线程读写数据:从主存中复制数据--》执行代码修改数据--》用修改的数据刷新主存中的数据
实现线程安全几种方式:
1.使用多例 2.使用类库中的安全类 3.使用锁机制
锁机制
a.隐式锁(synchronized)
两种方式 1.在方法声明是加入 2.在代码块上面加入
线程锁的效率(依次提高)
public synchronized void addGrilFriend() {
}
public void addGrilFriend() {
synchronized (test.class) {
}
}
private byte [] lock=new byte[1];
public void addGrilFriend() {
synchronized (lock) {
}
}
第一种小于第二种是因为,第一种锁住了方法体,而线程进入方法体后,分配资源还需要一定的时间
最后一种加锁对象占用资源最小,释放加锁更加高效
b.显示锁(Lock)
Lock是一个接口,提供了可轮询的,无条件的,定时的,可中断的锁机制
方法
ReentrantLock
比synchronized 更具伸展性,使用:
private ReentrantLock reLock=new ReentrantLock();
public void feel() {
int a=0;
//获得锁
reLock.lock();
try {
a+=1;
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放锁
reLock.unlock();
}
}
相对于synchronized: 更加灵活,但需要释放锁,只适用于代码块
ReadWriteLock
支持并发条件下一个资源大量被读,或者被一个写线程访问。不能同时存在读写线程,适用于大量读少量写的场景。
使用:
private ReentrantReadWriteLock reLock=new ReentrantReadWriteLock();
private Lock readLock=reLock.readLock();
private Lock writeLock=reLock.writeLock();
//为所有读加锁
public void readAddLock() {
readLock.lock();
try {
int a=0;
} catch (Exception e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
//为所有写加锁
public void writeAddLock() {
writeLock.lock();
try {
int a=0;
} catch (Exception e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
}
StampedLock
首先说一下悲观锁和乐观锁
悲观锁:每次拿数据的时候就去锁上。
乐观锁:每次去拿数据的时候,都没锁上,而是判断标记位是否有被修改,如果有修改就再去读一次。
StampedLock简介:StampedLock就是ReentrantReadWriteLock 的增强版。弥补了ReentrantReadWriteLock 的一些不足。如果在ReentrantReadWriteLock 中有大量的读和少量的写操作,根据其特性读-读不互斥,读-写互斥,写-写互斥,那么可能导致写饥饿问题(即是写一直无法抢到锁)
StampedLock读锁并不会阻塞写锁,而是当读的时候发现有写,就再次读一遍。
synchronized,ReentrantLock,ReentrantReadWriteLock,StampedLock对比
死锁:
线程A持有线程B往下执行需要的资源,线程B持有线程A下一步需要的资源,互相等待,陷入死锁
Volatile关键字
告诉编译器,变量是易变的,所以不要使用优化机制,而是每次使用都从内存中读取。可以保证可见性不能保证原子性
使用场景,多个线程读取变量,少量线程操作变量
原子操作atmoic
线程安全加强版的volatile操作。