并发学习笔记(三)——并发包(同步控制工具)

ReentrantLock(重入锁)

  • 可重入:单线程可以重复进入(次数),但要重复退出(相同次数)
public class TestReentrantLock implements Runnable {
    private static ReentrantLock lock = new ReentrantLock();
    private static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            lock.lock();
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestReentrantLock tl = new TestReentrantLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

单线程中,可重复进行lock()获得锁(内部记录次数),同样的需要执行相同次数unlock()。若lock>unlock,则线程结束后,其他线程无法获得锁从而阻塞;若lock<unlock,则会抛出IllegalMonitorStateException,监视器状态异常。

  • 可中断:lockInterruptibly()可中断式地加锁
public class TestReentrantLockInterrupt implements Runnable {
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();
    private int lock;

    /**
     * 控制加锁顺序,方便构成死锁
     */
    public TestReentrantLockInterrupt(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                // 可中断的加锁
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
                lock2.lockInterruptibly();
            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
                lock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread())
                lock1.unlock();
            if (lock2.isHeldByCurrentThread())
                lock2.unlock();
            System.out.println(Thread.currentThread().getId() + ":线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestReentrantLockInterrupt tl1 = new TestReentrantLockInterrupt(1);
        TestReentrantLockInterrupt tl2 = new TestReentrantLockInterrupt(2);
        Thread t1 = new Thread(tl1);
        Thread t2 = new Thread(tl2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        DeadlockChecker.check();
    }
}

死锁检查类DeadlockChecker.java

public class DeadlockChecker {
    private static final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
    final static Runnable deadlockCheck = new Runnable() {
        @Override
        public void run() {
            while (true) {
                long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
                if (deadlockedThreadIds != null) {
                    ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
                    for (Thread t : Thread.getAllStackTraces().keySet()) {
                        for (ThreadInfo threadInfo : threadInfos) {
                            if (t.getId() == threadInfo.getThreadId()) {
                                t.interrupt();
                            }
                        }
                    }
                }
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
            }
        }
    };

    public static void check() {
        Thread t = new Thread(deadlockCheck);
        t.setDaemon(true);
        t.start();
    }
}

注:当线程1和线程2形成死锁(使用可中断的加锁)时,使用Interrupt发送中断信号,可以中断线程,进入Catch块。

  • 可限时:超时不能获得锁,就返回false,不会永久等待构成死锁。
public class TestReentrantLockTime implements Runnable {
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6000);
            } else {
                System.out.println("Get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread())
                lock.unlock();
        }
    }

    public static void main(String[] args) {
        TestReentrantLockTime tl = new TestReentrantLockTime();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
    }
}

注:线程1线程2都需要同一把锁,拿不到锁的线程超时就会返回false,进入else分支。

  • 公平锁:先来先得。若不是公平锁,则先申请锁的,不一定先拿到锁;反之,公平锁一定是先来先得。公平锁性能不如非公平锁,处理排队。
	// ReentrantLock的一个构造函数
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

Condition(条件)

SynchronizedObject.wait/Object.notify配合使用。
ConditaionReentrantLock配合使用。

public class TestReentrantLockCondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    @Override
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread())
                lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestReentrantLockCondition tl = new TestReentrantLockCondition();
        Thread t1 = new Thread(tl);
        t1.start();
        Thread.sleep(2000);
        // 通知线程t1继续运行
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

说明:(1)线程1拿到锁(2)线程1等待条件,暂时释放锁(3)主线程拿到锁(4)主线程通知条件(5)主线程释放锁(6)线程1拿到锁(多个线程需要竞争锁),继续运行。
注:Condition.awaitUninterruptibly(),不会响应中断标志,Object.wait()当遇到中断标记时,会抛出异常。

Semaphore(信号量)

共享锁,可以允许多个线程进入临界区(PV操作中的信号量),带容量的。

  • void acquire():阻塞式的请求信号量。
  • void acquireUninterruptibly():不可中断阻塞式的请求信号量。
  • boolean tryAcquire():非阻塞请求信号量,立马返回。
  • boolean tryAcquire(long timeout,TIneUnit unit):阻塞式限时请求信号量,超时或成功返回结果。
  • void release():释放信号量。
public class TestSemapDemo implements Runnable {
    private final Semaphore semp = new Semaphore(5);
    private AtomicInteger i = new AtomicInteger(0);

    @Override
    public void run() {
        try {
            semp.acquire();
            // 模拟耗时操作
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + ":done " + i.incrementAndGet() + " !");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semp.release();
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(20);
        final TestSemapDemo demo = new TestSemapDemo();
        for (int i = 0; i < 20; i++) {
            pool.submit(demo);
        }
    }
}

代码说明:初始化了容量为5的信号量,每次请求都会减少1个,释放增加一个(回忆PV操作的信号量即可)。

ReadWriteLock(读写锁)

ReadWriteLock是JDK5提供的读写分离锁。
读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写,写也会阻塞读。
写-写互斥:写写阻塞。

非阻塞阻塞
阻塞阻塞
// 主要方法
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock  readLock = lock.readLock();
// 读写锁的方法和ReentrantLock一致.
// lock还可以获得读锁的数量,判断写是否被锁定等方法
// public int getReadLockCount()
// public boolean isWriteLocked()
// 拿到等待线程列表等
// 具体可看读写锁,和面用到在详细记录.

CountDownLatch(倒数计时器)

构造函数初始化一个数量,每次countDown,都会减1(CAS操作)。

public class TestCountDownLatch implements Runnable {
    static final CountDownLatch end = new CountDownLatch(10);
    static final TestCountDownLatch demo = new TestCountDownLatch();

    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete!");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            threadPool.submit(demo);
        }
        // 等待检查
        end.await();
        System.out.println("BOOM!");
        threadPool.shutdown();
    }
}

类似于方便的同步操作,先序任务完成后,后续线程才可以进行。

CyclicBarrier(循环栅栏)

Cyclic意为循环,也就是说栅栏可以重复使用。比如,设置为10,一批10个线程集合后,计数器归零,执行指定操作,然后进行下一轮集合。
构造函数:public CyclicBarrier(int parties, Runnable barrierAction),栅栏的大小和一次循环后需要执行的操作。

public class TestCyclicBarrier {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier barrier;

        Soldier(CyclicBarrier barrier, String name) {
            this.soldier = name;
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                barrier.await();
                doWork();
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
            }
        }

        private void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + "完成任务!");
        }
    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int n) {
            this.flag = flag;
            N = n;
        }

        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:士兵" + N + "个,任务完成!");
                flag = false;
            } else {
                System.out.println("司令:士兵" + N + "个,集合完毕!");
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        CyclicBarrier barrier = new CyclicBarrier(N, new BarrierRun(flag, N));
        System.out.println("集合队伍!");
        for (int i = 0; i < N; i++) {
            System.out.println("士兵" + i + "报道");
            allSoldier[i] = new Thread(new Soldier(barrier, "士兵" + i));
            allSoldier[i].start();
            //if (i == 5) {
            //    allSoldier[0].interrupt();
            //}
        }
    }
}

10个士兵集合完毕,才可以执行任务,同步问题,用CyclicBarrier可以实现,且可以完成多组士兵集合执行任务。
(1)首先,士兵开始集合,进行await(),阻塞式的等待。
(2)10次后,激活Runnable任务,集合完毕。
(3)执行任务,进行await(),阻塞式的等待。
(4)10次后,激活Runnable任务,任务完成。
这个任务是可以中断的,当中断后抛出InterruptedException异常,而等待在该栅栏上的其他线程会抛出BrokenBarrierException异常(不会集合完成,没有意义的等待)。

LockSupport

提供线程阻塞原语。

  • LockSupport.park():挂起线程
  • LockSupport.unpark(t1):停止挂起线程
    与suspend()的区别:不容易引起线程冻结(resume发生在suspend前,后调用suspend造成永久性阻塞)。
    park是不会抛出异常的,但是会响应中断(Interrupt)标记。
    park后主要有两种方式可以继续运行,(1)Thread.unpark(t1)(2)t1.interrupt
public class TestLockSupport {
    private static final Object u = new Object();
    private static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    private static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        t2.start();
        Thread.sleep(100);
//        t2.interrupt();
//        t1.interrupt();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

ReentrantLock的实现

(1)内部通过CAS状态判断是否修改成功。

// 非公平锁
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

比较和设置状态State,成功则拿到排他锁,立即返回。
(2)如果没拿到锁,线程就要进入等待队列,队列中的线程都要执行park

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

源码中可以看到,最后没有拿到锁,队列中的线程需要去parkAndCheckInterrupt,也就是park去挂起。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值