多线程基础(四)Lock

1. Lock
Lock的实现原理和Synchronized完全不同,它使用compare and swap理念,如果符合cas判定逻辑,那么就修改state状态,把当前线程设定为独占,无法获取锁就加入到等待队列。

2. Lock实现同步

import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest2 {
    private static ReentrantLock lock = new ReentrantLock();
    static class ThreadC extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---start");
                Thread.sleep(1000L);
                System.out.println(Thread.currentThread().getName() + "---end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            ThreadC thread = new ThreadC();
            thread.setName("线程:" + i);
            thread.start();
        }
    }
}

执行结果,可以看到各个线程是排队执行的

线程:0---start
线程:0---end
线程:1---start
线程:1---end
线程:2---start
线程:2---end
线程:3---start
线程:3---end
线程:4---start
线程:4---end
线程:5---start
线程:5---end
线程:6---start
线程:6---end
线程:7---start
线程:7---end
线程:8---start
线程:8---end
线程:9---start
线程:9---end

3. 实现部分通知
synchornized虽然有wait、notify、notifyAll,但是notify是随机通知,notifyAll是全部通知,没办法做到通知固定的线程
使用Lock配合Condition可以实现通知指定线程的功能
Condition的await、signal、signalAll分别和Object类的wait、notify、notifyAll对应

public class ThreadTest2 {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition conditionA = lock.newCondition();
    private static Condition conditionB = lock.newCondition();

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---start");
                conditionA.await();
                System.out.println(Thread.currentThread().getName() + "---end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---start");
                conditionB.await();
                System.out.println(Thread.currentThread().getName() + "---end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new ThreadA().start();
        new ThreadB().start();
        Thread.sleep(3000L);
        System.out.println("等待3秒后唤醒A线程");
        try {
            lock.lock();
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
        Thread.sleep(3000L);
        System.out.println("再等待3秒后唤醒B线程");
        try {
            lock.lock();
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

执行结果

Thread-0---start
Thread-1---start
等待3秒后唤醒A线程
Thread-0---end
再等待3秒后唤醒B线程
Thread-1---end

4. 公平锁与非公平锁
公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即fifo先进先出顺序。非公平锁是随机获取锁抢占机制。
可以理解为公平锁调用了wait的线程在还没获取到锁的时候,线程相当于在一个先进先出的队列里,最终谁真正获取到锁是有先来后到的顺序的;
非公平锁可以理解为在一个无序集合中,最终谁获得锁完全是靠争抢随机的。上面两句话是为了大家能更好地理解

ReentrentLock构造方法可以传一个bool类型的参数,true代表公平锁,false代表非公平锁,默认是false。

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


    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---end");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ArrayList<Thread> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ThreadA thread = new ThreadA();
            thread.setName(i + "");
            list.add(thread);
        }
        list.forEach(Thread::start);
    }
}

运行结果:完全随机

0---end
1---end
2---end
4---end
6---end
8---end
3---end
7---end
5---end
9---end

把Lock构造方法的false改为true,使用公平锁结果如下

0---end
1---end
2---end
3---end
4---end
5---end
6---end
7---end
8---end
9---end

5. lockInterruptibly
看方法名字我们就知道这个方法和中断标记有关
经测试此方法只对那些线程(线程A)已经开始,还未获取到锁的线程,此时如果其他线程(线程B)设置了中断标记,则线程A会被唤醒处理中断异常。
只有等待获取锁时才会发生中断异常。如果线程还未处于等待获取锁的状态则不会发生中断异常

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

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                lock.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + "---start");
                for (int i = 0; i < 100000000; i++) {
                    Math.random();
                }
                System.out.println(Thread.currentThread().getName() + "---end");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "响应中断");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadA thread = new ThreadA();
        ThreadA thread2 = new ThreadA();

        thread.setName("A");
        thread2.setName("B");

        //thread2.interrupt();
        //thread.interrupt();
        //Thread.sleep(500L);

        thread.start();
        thread2.start();

        Thread.sleep(500L);

        thread2.interrupt();
        thread.interrupt();
    }
}

一、start之前调用中断,两个线程仍然正常执行完,所以线程start前设置中断是不管用的。
二、 thread.start();
thread2.start();
thread2.interrupt();
thread.interrupt();
这种情况可以响应中断
三、thread.start();
thread2.start();
Thread.sleep(100L);
thread2.interrupt();
thread.interrupt();
这种情况先开始并且获取到锁的线程可以正常执行完,处于等待的线程会响应中断

6. TryLock
尝试获取锁,如果获取成功返回true,否则返回false

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

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                if (lock.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + "---start");
                    for (int i = 0; i < 100000000; i++) {
                        Math.random();
                    }
                    System.out.println(Thread.currentThread().getName() + "---end");
                } else {
                    System.out.println(Thread.currentThread().getName() + "---没有获取到锁");
                }
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadA thread = new ThreadA();
        ThreadA thread2 = new ThreadA();
        thread.setName("A");
        thread2.setName("B");

        thread.start();
        thread2.start();
    }
}

运行结果

B---start
A---没有获取到锁
B---end

将上述获取锁的代码改为下面的代码,代表着尝试获取锁,到时只尝试1秒,一秒后如果还获取不到就返回false

lock.tryLock(1L, TimeUnit.SECONDS)

循环部分执行时间大约4秒
执行结果

A---start
B---没有获取到锁
A---end

如果改为

lock.tryLock(10L, TimeUnit.SECONDS)

执行结果

A---start
A---end
B---start
B---end

7. awaitUninterruptibly
wait、await、sleep三个方法都可以是线程暂停,暂停期间也都可以响应中断,如果发生中断,则会抛出中断异常
condition.awaitUninterruptibly():可以实await不响应中断

public class ThreadTest3 {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                if (lock.tryLock(10L, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + "---start");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "---end");
                } else {
                    System.out.println(Thread.currentThread().getName() + "---没有获取到锁");
                }
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "---响应中断");
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadA thread = new ThreadA();
        thread.start();
        Thread.sleep(1000L);
        thread.interrupt();

    }
}

执行结果:

Thread-0---start
Thread-0---响应中断

把上面的await()换成condition.awaitUninterruptibly(); 执行结果:Thread-0—start
程序将一直等待signal();
**condition.awaitUntil()方法:**接受一个Date对象,标识等待到某一时间,否则就中断

8.Lock的其他方法
lock.getHoldCount():获取当前线程保持此锁定的个数,就是指当前线程调用了几次lock
lock.getQueueLength():返回正在等待获取此锁的线程估计数
lock.getWaitQueueLength(condition):返回等待与此锁给定的Condition相关的线程估计数
lock.hasQueuedThread(currentThread()):查询指定线程是否等待获取此锁
lock.hasQueuedThread():是否有线程正在等待此锁
hasWaiters(condition):是否有线程在等待与此锁指定有关的condition条件
lock.isFair():返回是否公平锁
lock.isHeldByCurrentThread():当前线程是否保持此锁定
lock.isLocked():查询此锁定是否由人以线程保持

9. ReentrantReadWriteLock
Lock常用的实现一共有两个,我们之前用的ReentrantLock和ReentrantReadWriteLock
ReentrantLock:同一个锁具有完全互斥排它的效果,虽然可以保证线程安全,但是在某些情况下,比如有读有写都需要加加锁,并且读方法的并发较高,此时用ReentrantLock显然效率会比较低。
而ReentrantReadWriteLock:内部有两个内部类ReadLock和WriteLock,称为读锁和写锁,也叫共享锁与排它锁
简单来总结就是读读不互斥,读写、写读、写写都是互斥的。
我们这里指演示一个先读后写的例子:

public class ReentrantReadWriteLockTest {
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                lock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + ":0:" + System.currentTimeMillis());
                Thread.sleep(1000L);
                System.out.println(Thread.currentThread().getName() + ":1:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "---响应中断");
            } finally {
                lock.readLock().unlock();
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            try {
                lock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + ":0:" + System.currentTimeMillis());
                Thread.sleep(1000L);
                System.out.println(Thread.currentThread().getName() + ":1:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "---响应中断");
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ThreadA threadA = new ThreadA();
        threadA.setName("读");
        threadA.start();

        ThreadB threadB = new ThreadB();
        threadB.setName("写");
        threadB.start();
    }
}

执行结果:

读:0:1566019964339
读:1:1566019965341
写:0:1566019965341
写:1:1566019966341

10. Timer
本想演示一下Timer的但是觉得用不到就不写了,因为Timer在时间系统变化或者其它一些情况下会有一些坑,所以不建议大家使用Timer做定时任务,推荐大家使用juc下的ScheduledThreadPoolExecutor、或quarts、分布式:es-job、xxl-job

发布了52 篇原创文章 · 获赞 7 · 访问量 3818

猜你喜欢

转载自blog.csdn.net/maomaoqiukqq/article/details/99682035