公平锁和非公平锁
公平锁:
公平锁,就是很公平,在并发环境中每个线程在获取锁时首先会查看此锁维护的等待队列,如果是空说明自己是第一个就会获得到锁,如果不为空就会加入到队列中等待排队。
非公平锁:
非公平锁上来就尝试占有锁,如果尝试失败,才会加入到队列中。
多线程获取锁并不是按照申请锁顺序,有可能后申请锁的线程比先申请锁的线程优先获取锁,有可能会造成优先级反转或者饥饿现象(等了好久都没获得到锁非常饥饿)。
java.util.concurrent包中的ReentrantLock类,可以在这个类中的构造方法中传入一个Boolean类型值,如果是true代表公平锁,如果false是非公平锁,什么也不传默认是非公平锁。
非公平锁的优点在于比公平锁的吞吐量要大,并发效率要高。
Synchronized就是典型的非公平锁。
可重入锁(又名递归锁)
理论:是指同一线程外层函数获得到锁之后,内层函数会自动获得到锁。
其实就是同步方法中调用另一个同步方法,线程获取到外层函数锁,内层锁自动获取到。
例如:代码如下
public class Reentrant {
public static synchronized void method(){ System.out.println(Thread.currentThread().getName()+"mthood..");
method01();
}
public static synchronized void method01(){
System.out.println(Thread.currentThread().getName()+"method01.");
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Reentrant.method();
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
Reentrant.method();
}
},"t2").start();
}
}
输出结果:
t1mthood......
t1method01.....
t2mthood......
t2method01.....
最典型的可重入锁除了Synchronized外还有ReentrantLock,上边代码是Synchronized重入锁的代码。
ReentrantLock代码演示。
public class Reentrant01 {
static Lock lock = new ReentrantLock();
public static void set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"set...");
get();
} finally {
lock.unlock();
}
}
public static void get(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"get......");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Reentrant01.set();
}
},"t3").start();
new Thread(new Runnable() {
@Override
public void run() {
Reentrant01.set();
}
},"t4").start();
}
}
结果:
t3set...
t3get......
t4set...
t4get......
注意:lock.lock()几次就unlock()几次,只要匹配就不会出现死锁。
可重入锁避免了死锁的发生。
自旋锁(CAS思想)
尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁,减少了线程上下文切换所消耗的性能,但同样如果循环次数过度会占用CPU资源。
循环+CAS(比较并交换)的思想。
AtomicReference(原子引用)类
手写自旋锁代码如下:
public class zixuan {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void Mylock(){
do{
Thread thread = Thread.currentThread(); //获得当前线程
}while(!atomicReference.compareAndSet(null,thread));//比较并交换
}
public void UnMylock(){
//看内存中是不是当前线程对象,如果是把它设置为null
atomicReference.compareAndSet(Thread.currentThread(),null);
}
}
独占锁
指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁。
共享锁
指该锁可以被多个线程所持有。
对ReentrantReadWriteLock其实就是共享锁,其写锁是独占锁,该锁的共享锁保证并发读是非常高效的。
读读能共存(读的时候线程可以争抢)
读写互斥
写写互斥
代码如下:
/**
* 读写锁,读写共存,读写同时进行,
* 读和写共用一把锁,写可以保证写的原子性,中间没有加塞的现象,都是一个线程一个线程执行。
* 读一起读,提高并发率
*/
public class ReentrantReadWLock {
public volatile Map<String,Object> map = new HashMap<>();
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
/**
* 写方法
*/
public void put(String key,Object val){
reentrantReadWriteLock.writeLock().lock(); //加入写锁
try {
System.out.println(Thread.currentThread().getName()+"正在写...");
map.put(key,val);
System.out.println(Thread.currentThread().getName()+"写完成...");
}finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
/**
* 读方法
*/
public void get(String key){
reentrantReadWriteLock.readLock().lock(); //加入读锁
try {
System.out.println(Thread.currentThread().getName()+"正在读...");
map.get(key);
System.out.println(Thread.currentThread().getName()+"读完成...");
}finally {
reentrantReadWriteLock.readLock().unlock();
}
}
public static void main(String[] args) {
ReentrantReadWLock reentrantReadWLock = new ReentrantReadWLock();
for (int i=1;i<=5;i++){
int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
reentrantReadWLock.put(temp+"",temp+"");
}
},String.valueOf(i)).start();
}
for (int i=1;i<=5;i++){
int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
reentrantReadWLock.get(temp+"");
}
},String.valueOf(i)).start();
}
}
}
结果:
1正在写...
1写完成...
2正在写...
2写完成...
3正在写...
3写完成...
4正在写...
4写完成...
5正在写...
5写完成...
1正在读...
2正在读...
3正在读...
5正在读...
4正在读...
1读完成...
2读完成...
3读完成...
5读完成...
4读完成...
总结:
ReentrantReadWriteLock是共享锁,这个类里面有ReadLock()方法获得读锁,WriteLock()方法获得写锁。写的时候只能一个线程一个线程获得锁,中间不能加塞,保证写的原子性。读的时候可以多线程一起读,提高并发率。
举个例子:
写的时候为保证数据的原子性,在写的时候上锁只能由一个线程来写,写完了再由另一个线程写。不能正在写的时候被另一个线程加塞,破坏写的数据的原子性。而读就不需要,读可以多个线程一起读,就好比飞机场的公示牌如果只允许一个人一个人读岂不是太慢了,大家一起读提高效率,一个人表示一个线程。