实战Java高并发程序设计(3.1同步控制)

3.1重入锁

重入锁使用java.util.concurrent.locks.ReentrantLock来实现

public class Test implements Runnable {

    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

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

    public static void main(String[] args) throws Exception {
        Test t1 = new Test();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(i);
    }
}

重入锁与synchronized相比有明显的操作过程,开发人员必须指定何时加锁,何时解锁。同时,重入锁是可以反复添加的。一个县城可以连续两次获得同一把锁。

    lock.lock();
    lock.lock();
    try {
        i++;
        } finally {
            lock.unlock();
            lock.unlock();
        }

3.1.1重入锁可以被中断

public class Test implements Runnable {

    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    /**
     * 控制加锁顺序,方便构成死锁
     *
     * @param lock
     */
    public Test(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.err.println(Thread.currentThread().getId() + "退出!");
        }
    }

    public static void main(String[] args) throws Exception {
        Test t1 = new Test(1);
        Test t2 = new Test(2);
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        System.out.println("thread1: "+thread1.getId());
        System.out.println("thread2: "+thread2.getId());
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        thread2.interrupt();//③给t2线程状态标记为中断
    }
}

t1、t2线程开始运行时,会分别持有lock1和lock2而请求lock2和lock1,这样就发生了死锁。但是,在③处给t2线程状态标记为中断后,持有重入锁lock2的线程t2会响应中断,并不再继续等待lock1,同时释放了其原本持有的lock2,这样t1获取到了lock2,正常执行完成。t2也会退出,但只是释放了资源并没有完成工作。

3.1.2锁申请等待限时

可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。

前者不带参数,这时线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁。
后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。

public class Test implements Runnable {
    public 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) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
    }
}

tryLock()方法接受的两个参数,一个表示等待时长,另一个表示计时单位。在本例中,由于占用锁的进程持有锁长达6秒,另一个线程无法再5秒的等待时间中得到锁,会返回false。

3.2公平锁

在大多数情况下,锁的申请并不是公平的,先申请的线程并不一定在锁可用时先使用。要实现公平锁需要系统维护一个有序数列,性能较低,因此,默认情况下,锁都是非公平的。

public class Test implements Runnable {
    /**
     * 默认为false,表示为非公平锁
     */
    public static ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"获得锁");
            }finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
    }
}

两个线程会交替输出。如果是非公平锁:

线程1显示一大堆,然后线程2显示一大堆。一个线程会倾向于再次获取已经持有的锁。

3.3Condition条件

await()方法会使当前线程等待,同时释放当前锁,当其他线程使用signal()或者signalAll()方法时,线程会重新获得锁并执行。

public class Test 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 {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException{
        Test test = new Test();
        Thread t1 = new Thread(test);
        t1.start();
        Thread.sleep(2000);
        //通知线程继续执行
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

3.4信号量(Semaphore)

信号量可以指导多个线程同时访问某一资源。

acquire()方法尝试获取一个准入许可,若无法获得,则线程等待。

tryAcquire()方法尝试获取一个许可,成功返回true,失败返回false,不会等待。

release()用于线程访问完资源后,释放许可。

public class Test implements Runnable {
    final Semaphore semaphore = new Semaphore(5);

    @Override
    public void run() {
        try {
            semaphore.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId()+" done");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException{
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        final Test test = new Test();
        for (int i = 0;i<20;i++){
            executorService.submit(test);
        }
    }
}

5个线程为一组同时访问资源,但线程的顺序随机。

3.5ReadWriteLock读写锁

读写锁允许多个线程同时读,但写写操作和读写操作间依然需要相互等待及持有锁。

public class Test {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    private int value;

//    模拟读操作
    public Object handleRead(Lock lock)throws InterruptedException{
        try {
            lock.lock();
//            读操作耗时越多,读写锁的优势越明显
            Thread.sleep(1000);
            return value;
        } finally {
            lock.unlock();
        }
    }

//    模拟写操作
    public void handleWrite(Lock lock,int index) throws InterruptedException{
        try {
            lock.lock();
            Thread.sleep(1000);
            value = index;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final Test test = new Test();
        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    test.handleRead(readLock);
//                    test.handleRead(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    test.handleWrite(writeLock,new Random().nextInt());
//                    test.handleWrite(lock,new Random().nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0;i<18;i++){
            new Thread(readRunnable).start();
        }
        for (int i = 18;i<20;i++){
            new Thread(writeRunnable).start();
        }
    }
}

读线程和写线程可以并行,写会阻塞读,所以这段代码运行2秒多就结束,如果不用读写锁,则需要20多秒。

3.6倒计时器:CountDownLatch

例如火箭发射倒计时,完成10项检查才能发射火箭。

public class Test implements Runnable {
//    标明要完成10个任务
    static final CountDownLatch end = new CountDownLatch(10);
    static final Test test = new Test();

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

    public static void main(String[] args) throws InterruptedException{
//        创建10个任务
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.submit(test);
        }
//        等待检查,要求主线程完成所有任务后才能执行
        end.await();
        System.out.println("Fire!");
        service.shutdown();
    }
}

3.7循环栅栏:CyclicBarrier

假设司令下达命令,要求10个士兵一起去完成一项任务。士兵需要先集合,再去完成任务。

public class Test {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic, String soldierName) {
            this.cyclic = cyclic;
            this.soldier = soldierName;
        }

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

        @Override
        public void run() {
            try {
//                确定是否都集合完毕
                cyclic.await();
                doWork();
//                确定是否都完成工作
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

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

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

        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成]");
            } 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 cyclic = 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(cyclic, "士兵" + i));
            allSoldier[i].start();
        }
    }
}

3.8线程阻塞工具类:LockSupport

LockSupport可以在线程中的任何位置让线程阻塞。

public class Test {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread{
        public 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();
        Thread.sleep(100);
        t2.start();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_33283652/article/details/82855942