1.公平锁和非公平锁

  • 公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
  • 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

并发包中ReentrantLock的创建可以指定构造函数Boolean类型来得到公平锁或非公平锁,默认是非公平锁。

关于两者的区别:

  • 公平锁:公平锁就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
  • 非公平锁:非公平锁比较粗糙,上来就直接尝试占有锁,如果尝试失败,就再采取类似公平锁那种方式。

Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的有点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。

2.可重入锁(又名递归锁)

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程的外层方法获取锁的时候,在进入内层方法会自动获取锁,也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
可重入锁最大的作用是避免死锁

  • 2.1.synchronized就是一个典型的可重入锁
    示例代码:
class Phone {

    public synchronized void sendSMS() {

        System.out.println(Thread.currentThread().getName() + "\tsendSMS()");

        sendEmail();
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\tsendEmail()");
    }
}

public class ReentrantLockDemo {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendSMS();
        }, "t1").start();

        new Thread(() -> {
            phone.sendSMS();
        }, "t2").start();
    }
}

运行结果:

t1	sendSMS()   //t1线程在外层方法获取锁的时候
t1	sendEmail()  //t1在进入内存方法会自动获取锁
t2	sendSMS()
t2	sendEmail()
  • 2.2ReentrantLock也是典型可重入锁
    示例代码:
class Phone {

    Lock lock = new ReentrantLock();

    public void sendSMS() {
        lock.lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\tsendSMS()");

            sendEmail();

        } finally {
            lock.unlock();
        }


    }

    public void sendEmail() {
        lock.lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\tsendEmail()");

        } finally {
            lock.unlock();
        }
    }
}

public class ReentrantLockDemo {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendSMS();
        }, "t1").start();

        new Thread(() -> {
            phone.sendSMS();
        }, "t2").start();
    }
}

运行结果:

t1	sendSMS()     //t1线程在外层方法获取锁的时候
t1	sendEmail()    //t1在进入内存方法会自动获取锁
t2	sendSMS()
t2	sendEmail()

3.自旋锁(spinlock)

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
如AtomicInteger

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

手动实现自旋锁:

public class SpinlockDemo {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        System.out.println(Thread.currentThread().getName() + "\t try get lock");

        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {

        }

        System.out.println(Thread.currentThread().getName() + "\t get lock");
    }

    public void myUnlock() {
        atomicReference.compareAndSet(Thread.currentThread(), null);

        System.out.println(Thread.currentThread().getName() + "\t unlock");
    }

    public static void main(String[] args) throws InterruptedException {

        SpinlockDemo spinlockDemo = new SpinlockDemo();

        new Thread(() -> {

            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            spinlockDemo.myUnlock();

        }, "AA").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {

            spinlockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            spinlockDemo.myUnlock();

        }, "BB").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {

            spinlockDemo.myLock();

            spinlockDemo.myUnlock();

        }, "CC").start();
    }
}

运行结果:

AA	 try get lock
AA	 get lock
BB	 try get lock
CC	 try get lock
AA	 unlock
CC	 get lock
CC	 unlock
BB	 get lock
BB	 unlock

4.独占锁(写锁),共享锁(读锁),互斥锁

独占锁:指该锁一次只能被一个线程所持有,对ReentrantLock和Synchronized而言都是独占锁。
共享锁:指该锁可被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
不加锁出现线程安全问题:

class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();

    public void set(String key, Object value) {

        System.out.println(Thread.currentThread().getName() + "\t 正在添加:" + key);
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 添加完成:" + key);
    }

    public Object get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
        return result;
    }
}

public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //write
        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                myCache.set(tmp + "", tmp + "");
            }, "t" + i).start();
        }


        //read
        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                myCache.get(tmp + "");
            }, "t" + (i + 5)).start();
        }
    }
}

运行结果:

t2	 正在添加:2
t4	 正在添加:4
t3	 正在添加:3
t5	 正在添加:5
t1	 正在添加:1
t6	 正在读取:1
t7	 正在读取:2
t8	 正在读取:3
t9	 正在读取:4
t10	 正在读取:5
t3	 添加完成:3
t7	 读取完成:2
t10	 读取完成:5
t8	 读取完成:null
t2	 添加完成:2
t9	 读取完成:null
t1	 添加完成:1
t6	 读取完成:null
t5	 添加完成:5
t4	 添加完成:4

添加读写锁保证线程安全:

class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();

    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void set(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在添加:" + key);

            TimeUnit.MICROSECONDS.sleep(300);

            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 添加完成:" + key);
        } catch (InterruptedException e) {

            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }

    }

    public Object get(String key) {
        rwLock.readLock().lock();
        Object result = null;
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
        } finally {
            rwLock.readLock().unlock();
        }
        return result;
    }
}

public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //write
        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                myCache.set(tmp + "", tmp + "");
            }, "t" + i).start();
        }


        //read
        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                myCache.get(tmp + "");
            }, "t" + i).start();
        }
    }
}

运行结果:

t2	 正在添加:2
t2	 添加完成:2
t1	 正在添加:1
t1	 添加完成:1
t3	 正在添加:3
t3	 添加完成:3
t5	 正在添加:5
t5	 添加完成:5
t4	 正在添加:4
t4	 添加完成:4
t1	 正在读取:1
t1	 读取完成:1
t2	 正在读取:2
t4	 正在读取:4
t5	 正在读取:5
t5	 读取完成:5
t3	 正在读取:3
t2	 读取完成:2
t3	 读取完成:3
t4	 读取完成:4

从运行结果上看,写锁(独占锁)是互斥的,读锁是共享的,在某些场景下,读写锁(ReentrantReadWriteLock)在保证线程安全的前提下比Synchronized和ReentrantLock(读写操作都上锁)的并发性更高。即也是读写分离的思想。

posted on 2021-03-18 10:46  whn051799  阅读(83)  评论(0编辑  收藏  举报