锁的总结

1.大纲

  Lock接口

  锁的分类

  乐观锁和悲观锁

  可重入锁与非可重入锁

  公平锁与非公平锁

  共享锁与排它锁

  自旋锁与阻塞锁

  可中断锁

  锁优化

一:Lock接口

1.锁

  是一种工具,用于控制对共享资源的访问

  Lock和synchronized,是常见的锁,都可以达到线程安全的目的

  Lock最常见的实现类是ReenTrantLock

  

2.为啥用Lock

  synchronized不够用

    效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在尝试获得锁的线程

    不够灵活:加锁与释放锁单一,每个锁仅有单一的条件

    无法知道是否成功获取锁

3.Lock的主要方法

  lock()

  tryLock()

  tryLock(long time, TimeUnit unit)

  lockInterruptibly()

4.lock

  获取最普通的获取锁,如果被其他线程获取,则进行等待

  不会像synchronized一样在异常的时候自动释放锁

  lock方法不能被中断,一旦陷入死锁,lock就会永久等待

package com.jun.juc.lock.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Lock必须手动释放锁
 */
public class MustUnLock {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try{
            //
            System.out.println(Thread.currentThread().getName()+"-run");
        }finally {
            lock.unlock();
        }
    }
}

  

5.tryLock

  用来尝试获取锁,如果被其他线程占用,则获取成功,返回true,否则返回false,代表获取锁失败

  功能比lock强大了,可以根据是否获取到锁,决定后续程序的行为

  立刻返回

6.tryLock(long time, TimeUnit unit)

  超时就放弃

  可以避免死锁

package com.jun.juc.lock.lock;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * tryLock避免死锁
 */
public class TryLockDeadLock implements Runnable {
    int flag = 1;

    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        TryLockDeadLock tryLockDeadLock1 = new TryLockDeadLock();
        TryLockDeadLock tryLockDeadLock2 = new TryLockDeadLock();
        tryLockDeadLock1.flag = 1;
        tryLockDeadLock2.flag = 0;
        new Thread(tryLockDeadLock1).start();
        new Thread(tryLockDeadLock2).start();
    }

    @Override
    public void run() {
        if (flag == 1) {
            try {
                for (int i = 0; i < 100; i++) {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("1获取了lock1");
                            Thread.sleep(new Random().nextInt(1000));
                            // 获取第二把锁
                            if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                                try {
                                    System.out.println("1获取了lock2");
                                    System.out.println("1成功获取两把锁");
                                    break;
                                } finally {
                                    lock2.unlock();
                                    Thread.sleep(new Random().nextInt(1000));
                                }
                            } else {
                                System.out.println("1获取lock2失败,在重试");
                            }
                        } finally {
                            // 因为上面获取到了锁,需要释放
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("1获取lock1失败,在重试");
                    }
                }
            } catch (Exception e) {
                // 防止800ms内被中断
                e.printStackTrace();
            }
        }

        if (flag == 0) {
            try {
                for (int i = 0; i < 100; i++) {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("2获取了lock2");
                            Thread.sleep(new Random().nextInt(1000));
                            // 获取第二把锁
                            if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                                try {
                                    System.out.println("2获取了lock1");
                                    System.out.println("2成功获取两把锁");
                                    break;
                                } finally {
                                    lock1.unlock();
                                    Thread.sleep(new Random().nextInt(1000));
                                }
                            } else {
                                System.out.println("2获取lock1失败,在重试");
                            }
                        } finally {
                            // 因为上面获取到了锁,需要释放
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("2获取lock2失败,在重试");
                    }
                }
            } catch (Exception e) {
                // 防止800ms内被中断
                e.printStackTrace();
            }
        }
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:57057', transport: 'socket'
1获取了lock1
2获取了lock2
1获取lock2失败,在重试
2获取了lock1
2成功获取两把锁
1获取了lock1
1获取了lock2
1成功获取两把锁
Disconnected from the target VM, address: '127.0.0.1:57057', transport: 'socket'

  

7.lockInterruptipy

  把超时时间设置为无限,在过程中,线程可以被中断

package com.jun.juc.lock.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockInterruptibly implements Runnable {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptibly lockInterruptibly = new LockInterruptibly();
        Thread thread = new Thread(lockInterruptibly);
        Thread thread1 = new Thread(lockInterruptibly);
        thread.start();
        thread1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"尝试获取锁");
        try{
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName()+"拿到了锁");
                Thread.sleep(5000);
            }catch (Exception e){
                System.out.println("睡眠时间被打断");
            }finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName()+"释放了锁");
            }
        }catch (Exception e){
            System.out.println("等待锁时被打断");
            e.printStackTrace();
        }
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:62438', transport: 'socket'
Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-0拿到了锁
睡眠时间被打断
Thread-0释放了锁
Thread-1拿到了锁
Disconnected from the target VM, address: '127.0.0.1:62438', transport: 'socket'
Thread-1释放了锁

Process finished with exit code 0

  

8.可见性

  happens-before

  lock拥有可见性保障的

  

  

  

二:锁的分类

1.分类

  

三:乐观锁与悲观锁

 1.悲观锁的劣势

  也叫互斥同步锁

  劣势:

    阻塞和唤醒带来的性能劣势

    永久阻塞,如果持有锁的线程被永久阻塞,那么等待该线程释放的线程,永远都得不到执行

    优先级反转

  

2.悲观锁

  

3.乐观锁

  

   

   典型的案例就是原子类,并发容器等

4.乐观锁例子

package com.jun.juc.lock.lock;

import java.util.concurrent.atomic.AtomicInteger;

public class PessimismOptimismLock {
    public static void main(String[] args) {
        // 内部是乐观锁,也是安全的
        AtomicInteger atomicInteger = new AtomicInteger();
        int i = atomicInteger.incrementAndGet();
        System.out.println("===:"+i);
    }
}

  

5.悲观锁与乐观锁的使用场景

  悲观锁:适合并发写入多的情况,适用于临界区持锁时间较长的情况,悲观锁可以避免大量的无用的自旋消耗,典型情况:

    临界区有IO操作

    临界区代码复杂或者循环量大

    临界区竞争激烈

  乐观锁:适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅度提高

四:重入锁与非可重入锁

1.可重入锁与非可重入锁,ReentrantLock

package com.jun.juc.lock.reentrantlock;

import java.util.PrimitiveIterator;
import java.util.concurrent.locks.ReentrantLock;

public class CinemaBookSeat {
    private static ReentrantLock lock = new ReentrantLock();

    private static void bookSeat(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始预订座位");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"完成预订");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
        new Thread(()->bookSeat()).start();
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:50292', transport: 'socket'
Thread-0开始预订座位
Thread-0完成预订
Thread-1开始预订座位
Thread-1完成预订
Thread-2开始预订座位
Thread-2完成预订
Thread-3开始预订座位
Disconnected from the target VM, address: '127.0.0.1:50292', transport: 'socket'
Thread-3完成预订

  

2.另一个示例

package com.jun.juc.lock.reentrantlock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {
    static class Outpurer{
        Lock lock = new ReentrantLock();
        public void output(String name){
            int length = name.length();
            lock.lock();
            try{
                for (int i=0;i<length;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println("打印完成");
            }finally {
                lock.unlock();
            }
        }
    }

    private void init(){
        final Outpurer outpurer = new Outpurer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try{
                        Thread.sleep(500);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    outpurer.output("abcdefg");
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try{
                        Thread.sleep(500);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    outpurer.output("123456");
                }
            }
        }).start();
    }

    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        lockDemo.init();
    }
}

  

3.演示可重入

  一个线程可以多次拿到这把锁,无需释放锁,就可以直接获取到。

  也叫递归锁。

  好处:

    避免死锁:如果有两个方法,都被一把锁锁住,运行到第一个方法拿到这把锁,运行到第二个方法,如果不是可重入锁,就需要等锁释放才可以获取,就会造成死锁。

    提升封装性:避免一次次加锁解锁

package com.jun.juc.lock.reentrantlock;

import javax.swing.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 可重入锁实验
 */
public class GetHoldCount {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        System.out.println(lock.getHoldCount());
        lock.lock();

        System.out.println(lock.getHoldCount());
        lock.lock();

        System.out.println(lock.getHoldCount());
        lock.lock();

        System.out.println(lock.getHoldCount());
        lock.unlock();

        System.out.println(lock.getHoldCount());
        lock.unlock();

        System.out.println(lock.getHoldCount());
        lock.unlock();

        System.out.println(lock.getHoldCount());
    }
}

  效果:

Disconnected from the target VM, address: '127.0.0.1:51619', transport: 'socket'
0
1
2
3
2
1
0

Process finished with exit code 0

  结论:

  很明显,继续加锁,前面没有解锁。是可重入锁。

4.另一个示例

package com.jun.juc.lock.reentrantlock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 递归锁
 */
public class RecursionDemo {
    private static ReentrantLock lock = new ReentrantLock();

    private static void accessResource() {
        lock.lock();
        try {
            System.out.println("已经对资源进行了处理");
            if (lock.getHoldCount() < 5) {
                System.out.println("before:"+lock.getHoldCount());
                accessResource();
                System.out.println("after:"+lock.getHoldCount());
            }
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        accessResource();
    }
}

  效果:

Disconnected from the target VM, address: '127.0.0.1:52207', transport: 'socket'
已经对资源进行了处理
before:1
已经对资源进行了处理
before:2
已经对资源进行了处理
before:3
已经对资源进行了处理
before:4
已经对资源进行了处理
after:4
after:3
after:2
after:1

Process finished with exit code 0

  

5.ReentrantLock的其他方法

  isHeldByCurrentThread:看出锁是否被当前的线程持有

  getQueueLength:返回当前正在等待这把锁的队列有多长,一般这按两个方法是开发和调试时候使用

五:公平锁与非公平锁

1.公平锁与非公平锁

  公平是指按照线程请求的顺序,来分配锁

  非公平指的是,不完全按照请求的顺序,在一定的情况下,可以插队

2.为什么需要非公平锁

  为了提高效率

  避免唤醒带来的空档期

  ReentrantLock,默认为非公平锁,true为公平锁,false为非公平锁

3.公平锁

package com.jun.juc.lock.reentrantlock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示公平锁与非公平锁
 */
public class FairLock {
    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Job(printQueue));
        }
        for (int i=0;i<10;i++){
            //依次执行
            threads[i].start();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Job implements Runnable {
    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始打印");
        printQueue.printJob();
        System.out.println(Thread.currentThread().getName() + "开始完毕");
    }
}

class PrintQueue {
    private Lock queueLock = new ReentrantLock(true);

    public void printJob() {
        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) +1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
            Thread.sleep(duration * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
        //再次打印
        queueLock.lock();
        try {
            int duration = new Random().nextInt(10) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
            Thread.sleep(duration * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:62693', transport: 'socket'
Thread-0开始打印
Thread-0正在打印,需要时间:2
Thread-1开始打印
Thread-2开始打印
Thread-3开始打印
Thread-4开始打印
Thread-5开始打印
Thread-6开始打印
Thread-7开始打印
Thread-8开始打印
Thread-9开始打印
Thread-1正在打印,需要时间:2
Thread-2正在打印,需要时间:2
Thread-3正在打印,需要时间:5
Thread-4正在打印,需要时间:3
Thread-5正在打印,需要时间:9
Thread-6正在打印,需要时间:10
Thread-7正在打印,需要时间:3
Thread-8正在打印,需要时间:4
Thread-9正在打印,需要时间:5
Thread-0正在打印,需要时间:8
Thread-0开始完毕
Thread-1正在打印,需要时间:3
Thread-2正在打印,需要时间:6
Thread-1开始完毕
Thread-2开始完毕
Thread-3正在打印,需要时间:6
Thread-3开始完毕
Thread-4正在打印,需要时间:9
Thread-4开始完毕
Thread-5正在打印,需要时间:5
Thread-5开始完毕
Thread-6正在打印,需要时间:7
Thread-6开始完毕
Thread-7正在打印,需要时间:7
Thread-7开始完毕
Thread-8正在打印,需要时间:1
Thread-8开始完毕
Thread-9正在打印,需要时间:2
Thread-9开始完毕
Disconnected from the target VM, address: '127.0.0.1:62693', transport: 'socket'

  结论:

  都是顺序的。重要的是,线程0执行完第一次锁的时候,想马上拿锁,是拿不到的,需要让线程9执行完,才能让线程0继续拿。

4.非公平锁

  将true修改为false

package com.jun.juc.lock.reentrantlock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示公平锁与非公平锁
 */
public class FairLock {
    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Job(printQueue));
        }
        for (int i=0;i<10;i++){
            //依次执行
            threads[i].start();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Job implements Runnable {
    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    // 如果一次全部打印完成,则是不公平;如果一次搞定了,则是不公平
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始打印");
        printQueue.printJob();
        System.out.println(Thread.currentThread().getName() + "开始完毕");
    }
}

class PrintQueue {
    private Lock queueLock = new ReentrantLock(false);

    public void printJob() {
        queueLock.lock();
        try {
            int duration = new Random().nextInt(5) +1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
            Thread.sleep(duration * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
        //再次打印
        queueLock.lock();
        try {
            int duration = new Random().nextInt(5) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要时间:" + duration);
            Thread.sleep(duration * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:62894', transport: 'socket'
Thread-0开始打印
Thread-0正在打印,需要时间:1
Thread-1开始打印
Thread-2开始打印
Thread-3开始打印
Thread-4开始打印
Thread-5开始打印
Thread-6开始打印
Thread-7开始打印
Thread-8开始打印
Thread-9开始打印
Thread-0正在打印,需要时间:2
Thread-0开始完毕
Thread-1正在打印,需要时间:3
Thread-1正在打印,需要时间:1
Thread-1开始完毕
Thread-2正在打印,需要时间:4
Thread-2正在打印,需要时间:1
Thread-2开始完毕
Thread-3正在打印,需要时间:4
Thread-3正在打印,需要时间:3
Thread-3开始完毕
Thread-4正在打印,需要时间:2
Thread-4正在打印,需要时间:5
Thread-4开始完毕
Thread-5正在打印,需要时间:1
Thread-5正在打印,需要时间:4
Thread-5开始完毕
Thread-6正在打印,需要时间:2
Thread-6正在打印,需要时间:5
Thread-6开始完毕
Thread-7正在打印,需要时间:2
Thread-7正在打印,需要时间:5
Thread-7开始完毕
Thread-8正在打印,需要时间:1
Thread-8正在打印,需要时间:3
Thread-8开始完毕
Thread-9正在打印,需要时间:3
Thread-9正在打印,需要时间:4
Disconnected from the target VM, address: '127.0.0.1:62894', transport: 'socket'
Thread-9开始完毕

Process finished with exit code 0

  结论:

  在第二个线程还没唤醒前,当前马上又获取到锁,继续执行

5.特例

  tryLock方法,不遵守设定的公平的规则

  当执行的时候,一旦有了线程释放锁,则可以直接获取到

6.公平锁与非公平锁的优缺点

  

六:共享锁与排它锁

1.共享锁与排它锁

  以ReentractReadWriteLock为例

  读锁的作用:

    多个读操作,不会有线程安全问题。在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁,读是无阻塞的,提高了效率

  读写的规则

    多个线程只申请读锁,都可以申请到

    如果有一个线程占用了读锁,如果要申请写锁,则申请写锁的线程会一直等待释放读锁

    如果,一个线程占用了写锁,其他线程申请写锁或者读锁,都是只能等待写锁的释放

2.读写锁示例

package com.jun.juc.lock.readwritelock;

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁
 */
public class CinemalLock {
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    private static void read(){
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->read(),"t1").start();
        new Thread(()->read(),"t2").start();
        new Thread(()->write(),"t3").start();
        new Thread(()->write(), "t4").start();
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:51054', transport: 'socket'
t1得到了读锁,在读取
t2得到了读锁,在读取
t1释放读锁
t2释放读锁
t3得到了写锁,在写
t3释放写锁
t4得到了写锁,在写
Disconnected from the target VM, address: '127.0.0.1:51054', transport: 'socket'
t4释放写锁

Process finished with exit code 0

  

3 读锁插队策略

  通过加true或者false判断是公平锁还是非公锁

  

   

   

   结论:

  

4.演示非公平锁的插队

package com.jun.juc.lock.readwritelock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class NonfairDemo {
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    private static void read(){
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->write(),"t0").start();
        new Thread(()->read(),"t1").start();
        new Thread(()->read(),"t2").start();
        new Thread(()->write(),"t3").start();
        new Thread(()->read(), "t4").start();
    }
}

  效果:

Connected to the target VM, address: '127.0.0.1:58198', transport: 'socket'
t0得到了写锁,在写
t0释放写锁
t1得到了读锁,在读取
t2得到了读锁,在读取
t1释放读锁
t2释放读锁
t3得到了写锁,在写
t3释放写锁
t4得到了读锁,在读取
Disconnected from the target VM, address: '127.0.0.1:58198', transport: 'socket'
t4释放读锁

Process finished with exit code 0

  

5.演示读锁的插队

  什么意思呢?

  在队列的头结点为写锁,则需要排队。要是读锁,则直接开始了读锁。

  怎么演示读锁是可以插队的呢,可以写一个子线程,不断的进行读插队,当开始读锁的时候,可以发现,每个读锁之间是有空闲期的,就可以出现了插队的出现了

演示代码:

package com.jun.juc.lock.readwritelock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class NonfairBargeDemo {
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    private static void read(){
        System.out.println(Thread.currentThread().getName()+ "尝试获取读锁");
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了读锁,在读取");
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        System.out.println(Thread.currentThread().getName()+ "尝试获取写锁");
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+ "得到了写锁,在写");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->write(),"t0").start();
        new Thread(()->read(),"t1").start();
        new Thread(()->read(),"t2").start();
//        new Thread(()->write(),"t3").start();
        new Thread(()->read(), "t4").start();
        new Thread(()->read(), "t5").start();
        new Thread(()->read(), "t6").start();
        new Thread(()->read(), "t7").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread[] threads = new Thread[2000];
                for (int i=0; i<2000; i++){
                    threads[i] = new Thread(()->read(),"子线程创建的线程" + i);
                }
                for (int i=0; i< 2000; i++){
                    threads[i].start();
                }
            }
        }).start();
    }
}

  说明:

  如果还没开始进行t2,t3的获取锁,子线程就执行完成了抢锁,就演示不出效果了

  

6.锁的升级与降级

  支持降级

  什么意思呢?

  在获取写锁,且不释放的情况下,可以接着获取读锁。

  不支持升级,只要是避免死锁。

  假设有两个线程,都在读,然后同时想进行升级到写锁,这个时候,就需要对方释放锁,然后,都不释放的时候,就陷入了死锁。所以,不支持升级。

  保证每一次只有一个锁在升级,才可以实现。

  

七:自旋锁与阻塞锁

1.说明

  

  

2.源码说明

  

3.实现一个自旋锁

package com.jun.juc.lock.spinlock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinLock {
    private AtomicReference<Thread> sign = new AtomicReference<Thread>();

    public void lock(){
        Thread current = Thread.currentThread();
        while (!sign.compareAndSet(null, current)){
            System.out.println(Thread.currentThread().getName() + "获取失败,再次尝试");
        }
    }

    public void unlock(){
        Thread current = Thread.currentThread();
        sign.compareAndSet(current, null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();

        Runnable runnable = new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() +"获取到了自旋锁");
                try{
                    Thread.sleep(300);
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName() +"释放到了自旋锁");
                    spinLock.unlock();
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();

    }
}

  

4.适用场景

  自旋锁一般用于多核的服务器,在并发度不是很高的情况下,比阻塞锁的效率高

  自旋锁适用于临界区比较短小的情况

八:可中断锁与不可中断锁

1.说明

  synchronized是不可中断锁,而lock是可中断锁,因为trylock(time)与lockInterruptibly都可以相应中断

九:锁优化

1.jvm

  自旋锁和自适应

  锁消除

  锁粗化

2.程序优化

  缩小同步代码块

  尽量不要锁住方法

   减少锁的次数

  避免人为制造热点

  锁中尽量不要包含锁

  选择合适锁类型或者合适的工具类

  

  

  

  

  

原文地址:https://www.cnblogs.com/juncaoit/p/13022440.html