在锁中有乐观锁和悲观锁
悲观锁会将资源锁住,不能多个线程同时的去访问,效率较低
乐观锁会在资源上加一个版本号,每次进行修改,会将版本号进行对比,如果相等,则进行修改,实际上,并不会对资源进行加锁底层时CAS,使用了一个自旋的方式去进行对比,交换
对于表锁,行锁
表锁会将一张表锁住,但行锁只会将一行内容上锁
但表锁不会造成死锁,因为死锁是因为多个线程将资源拿到,等待对方释放,再释放,但对于表锁,只有一个线程拿到资源,并不能造成死锁
但行锁可以造成死锁,比如第一个线程将第一行锁住,想要获取第二行,但第二个线程将第二行锁住,想要获取第一行,资源一直得不到释放,即发生死锁
对于写锁和读锁
写锁时独占锁,而读锁时共享锁,但两者都可能会造成死锁
对于写锁,线程一对第一行加读锁,线程二对第一行加读锁,但线程一,二都在等待对方的释放进行修改操作,所以会导致死锁
对于写锁,第一个线程将第一行锁住,想要获取第二行,但第二个线程将第二行锁住,想要获取第一行,资源一直得不到释放,即发生死锁
下面用一个例子来显示一下读写锁
当没有加读写锁时
public class WRDemo {
public static void main(String[] args) {
cache cache = new cache();
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
final String num= finalI +"";
try {
cache.put(num,num);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},String.valueOf(i)).start();
}
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
final String num= finalI +"";
try {
cache.get(num);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},String.valueOf(i)).start();
}
}
}
class cache{
private static final HashMap<String,Object> MAP=new HashMap<>();
public void put(String key,Object value) throws InterruptedException {
System.out.println(key+"号线程开始写");
TimeUnit.SECONDS.sleep(3);
MAP.put(key,value);
System.out.println(key+"号线程写完");
}
public void get(String key) throws InterruptedException {
Object result=null;
System.out.println(key+"号线程开始读");
TimeUnit.SECONDS.sleep(3);
result= MAP.get(key);
System.out.println(key+"号线程读到了"+result);
}
}
结果显示:一号线程没有写完时,就有读一号线程,所以读到了null
所以要引入读写锁ReentrantReadWriteLock
代码如下:
public class WRDemo {
public static void main(String[] args) {
cache cache = new cache();
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
final String num= finalI +"";
cache.put(num,num);
},String.valueOf(i)).start();
}
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
final String num= finalI +"";
cache.get(num);
},String.valueOf(i)).start();
}
}
}
class cache{
private static final HashMap<String,Object> MAP=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
public void put(String key,Object value){
readWriteLock.writeLock().lock();//加写锁
try {
System.out.println(key+"号线程开始写");
TimeUnit.SECONDS.sleep(3);
MAP.put(key,value);
System.out.println(key+"号线程写完");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();
try {
Object result=null;
System.out.println(key+"号线程开始读");
TimeUnit.SECONDS.sleep(3);
result= MAP.get(key);
System.out.println(key+"号线程读到了"+result);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
readWriteLock.readLock().unlock();
}
}
}
结果:当写完后才可以读
为什么会有读写锁
当无锁时,多个线程共同争抢一个资源,会显得特别的乱
而当有锁时比如synchronized和ReentrantLock是独占的,每次都只能是一个操作
但当引入读写锁时,问题就不一样了,即在现实中,读操作远远大于写操作,所以引入了读锁是共享的,而写锁是独占的
再来讨论一下读写锁降级
写锁可以降级到读锁,但读锁不可以升级到写锁
开始写----->开始读------>写完------->读完
就比如抄作业,可以当一个人边写,你边抄,当他写完了,你也抄完了
我们来验证一下
ReadWriteLock lock=new ReentrantReadWriteLock();
lock.writeLock().lock();
System.out.println("开始写");
lock.readLock().lock();
System.out.println("写完了");
lock.writeLock().unlock();
lock.readLock().unlock();
结果如下
但如果先读再写:
ReadWriteLock lock=new ReentrantReadWriteLock();
lock.readLock().lock();
lock.writeLock().lock();
System.out.println("开始写");
lock.readLock().unlock();
System.out.println("写完了");
lock.writeLock().unlock();
结果:从而不能降级