2.同步控制

一、重入锁ReentrantLock

1. 常用方法

public static ReentrantLock lock = new ReentrantLock();
lock.lock();//获得锁,如果锁已被占用,则等待
lock.lockInterruptibly();//获得锁,但优先响应中断
lock.tryLock();//尝试获得锁,若成功则返回true,若当前锁被其他线程占用,申请锁失败,则返回false,该方法不等待,因此也不会产生死锁
lock.tryLock(long time, TimeUnit unit);//在给定时间内尝试获得锁
lock.unlock();//释放锁

2. 简单的重入锁使用案例

public class Create implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    @Override
    public void run() {
        for (int j = 0; j < 10000000; ++j) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException { }
}

3. 和synchronized的对比

和synchronized相比,重入锁有明显的操作过程,开发人员必须手动指定何时加锁,何时释放,因此,重入锁对逻辑控制的灵活性高于synchronized。
对于synchronized来说,若一个线程在等待锁,那么只会有两种情况
	1.获得这把锁继续运行
	2.保持等待
但是使用重入锁lockInterruptibly,线程则可以在等待过程中被中断

4. 重入的含义

锁可以反复使用,一个线程可以连续两次获得同一把锁,但需要注意的是,如果同一个线程多次获得锁,那么也必须释放相同次数
    若释放次数多了,就会得到一个java.lang.IllegalMonitorStateException异常
    若释放次数少了,那么相当于线程还持有这个锁,其他线程无法进入临界区
//重入锁实例
lock.lock();
lock.lock();
try{
     i++;
}finally {
    lock.unlock();
    lock.unlock();
}

5. 中断响应 lockInterruptibly

//demo
public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;
    public IntLock(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 {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
    }
}
//线程t1和t2启动后,t1先占用lock1,再占用lock2,t2先占用lock2,再占用lock1.因此两者很容易形成相互等待
//而使用lockInterruptibly,在main函数中对t2进行中断,此时t2会放弃lock1的申请,同时释放已获得的lock2,然后t1就可以获得lock2继续执行下去

6.锁申请等待限时tryLock

  //demo
public class IntLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    private static int i = 1;
    @Override
    public void run() {
        try {
            while (lock.isLocked()) {
                System.out.println(Thread.currentThread().getId() + " 线程被阻塞,等待中,第" + i++ + "次尝试");
                Thread.sleep(1000);
            }
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getId() + " 获得线程,开始休眠");
                Thread.sleep(6000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getId() + " 释放");
            if (lock.isHeldByCurrentThread()) lock.unlock();
        }
        System.out.println(Thread.currentThread().getId() + " 结束");
    }
    public static void main(String[] args) throws InterruptedException {
        IntLock r1 = new IntLock();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        t1.start();
        Thread.sleep(1000);
        t2.start();
        t2.join();
        System.out.println("MISSION COMPLETE");
    }
}

7. 公平锁

public static ReentrantLock lock = new ReentrantLock(boolean fair);
public static ReentrantLock lock = new ReentrantLock(true);//公平锁
在多数情况下,锁都是非公平的,例如线程1请求了锁A,然后线程2也请求获得锁A,非公平锁是随机分配给1或者2的,但是公平锁则是按照先到先得的理念分配锁,也就是先分配给1然后给2,

公平锁的一大特点是不会产生饥饿,只要你排队就一定能获得资源。虽然公平锁看起来很优美,但是公平锁实现内部必然要维持一个有序队列,因而效率很低,因此若没有特别需求则不需要使用公平锁

二、Condition

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
通过lock接口获得一个Condition对象,就可以让线程在合适的时间等待,或在某特定时刻得到通知,继续执行。
当线程使用 await()方法时,要求线程持有相关的重入锁,在 await()方法调用后,这个线程会释放这个锁,同理, signal()方法调用时,也要求线程先获得相关的锁,在调用完后,一定要释放锁,否则程序无法继续执行

1.基本方法

await();
await(long time, TimeUnit unit);
awaitNanos(long nanoTimeout);
awaitUninterruptibly();
awaitUntil(Date deadline);
signal();//唤醒一个等待中的进程
signalAll();//唤醒所有等待中的进程

2.和wait()、notify()方法的异同

wait()和notify()方法是与synchronized配合使用的,而Condition是与重入锁相关联的

3.demo

public class ReenterLockCondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    @Override
    public void run() {
        try {
            lock.lock();//当线程使用await()方法时,要求线程持有相关的重入锁
            condition.await();//await()方法调用后,这个线程会释放这个锁
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReenterLockCondition r1 = new ReenterLockCondition();
        Thread t1 = new Thread(r1);
        t1.start();
        Thread.sleep(2000);
        lock.lock();//Condition.signal()方法调用时,要求线程先获得相关的锁
        condition.signal();
        lock.unlock();//在调用完后,一定要释放锁,否则t1无法继续执行
    }
}

三、信号量Semaphore

信号量是对锁的扩展,无论是内部锁synchronized或重入锁ReentrantLock,一次都只允许一个线程访问资源,而信号量则可以指定多个线程同时访问某一个资源

1.构造函数

public Semaphore(int permits);
public Semaphore(int permits, boolean fair);//第二个参数指定是否公平

2.基本方法

semp.acquire();//尝试获得准入许可,若无法获得则等待
semp.acquireUninterruptibly();//和acquire相同,但是不响应中断
semp.tryAcquire();//尝试获得,成功返回true,反之返回false,不阻塞
semp.tryAcquire(long timeout,TimeUnit unit);
public void release();//释放一个许可

四、读写锁ReadWriteLock

读不会修改文档,所以所有读线程之间不需要阻塞,而写线程会对文档进行修改,所以读写线程,写写线程之间需要进行阻塞。

1.demo

public class ReadWriteDemo {
    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 ReadWriteDemo demo = new ReadWriteDemo();
        Runnable readRunnale = new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleRead(readLock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable writeRunnale = new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleWrite(writeLock, new Random().nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 18; i++)
            new Thread(readRunnale).start();
        for (int i = 18; i < 20; i++)
            new Thread(writeRunnale).start();
    }
}

五、倒计数器CountDownLatch

CountDownLatch的构造函数接受一个int作为参数,既这个计数器的计数个数 
public class test implements Runnable {
    static final CountDownLatch latch = new CountDownLatch(10);
    static final test t = new test();
    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            latch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++)
            exec.submit(t);
        latch.await();
        System.out.println("Fire");
        exec.shutdown();
    }
}

六、循环栅栏CyclicBarrier

基础功能和倒计数器相似,可以实现线程间的计数等待,但是功能更加强大
public CyclicBarrier(int parties,Runnable barrierAction);
//parties:计数器个数
//barrierAction:每次计数完成后,系统会执行的动作

实例

public class test {
    public static class Worker implements Runnable {
        private final CyclicBarrier cyclicBarrier;
        private final String id;
        Worker(CyclicBarrier cyclicBarrier, String id) {
            this.cyclicBarrier = cyclicBarrier;
            this.id = id;
        }
        @Override
        public void run() {
            try {
                cyclicBarrier.await();
                command();
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
        void command() {
            try {
                Thread.sleep(new Random().nextInt(10) * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(id + " 号完成任务");
        }
    }
    public static class BarrierRun implements Runnable {
        private final int N;
        private boolean flag;

        public BarrierRun(boolean flag, int N) {
            this.N = N;
            this.flag = flag;
        }
        @Override
        public void run() {
            if (flag) {
                flag = false;
                System.out.println(N + "个工人,集合完毕");
            } else
                System.out.println(N + "个工人,任务完成");
        }
    }
    public static void main(String[] args) {
        final int N = 5;
        Thread[] workers = new Thread[N];
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(true, N));
        for (int x = 0; x < N; x++) {
            System.out.println("工人" + x + "报道");
            workers[x] = new Thread(new Worker(cyclic, Integer.toString(x)));
            workers[x].start();
        }
    }
}

七、线程阻塞工具LockSupport

LockSupport是一个线程阻塞工具,可以在线程内任意位置让线程阻塞。
和Thread.suspend()方法相比,他弥补了由于resume()方法执行位置错误引发的异常
和Object.wait()方法相比,他不需要先获得某个对象的锁,也不会抛出InterruptedException异常

1.suspend、resume引发的异常

//案例
public class test {
    public static final Object u = new Object();
    public static class Change extends Thread {
        @Override
        public void run() {
            synchronized (u) {
                System.out.println("first");
                Thread.currentThread().suspend();
            }
        }
    }
    public static void main(String[] args) {
        Change change = new Change();
        change.start();
        change.resume();
    }
}
//当我们执行这段代码时,程序会输出first,但是程序不会退出,而是会挂起,这是因为时间顺序的缘故,resume发生在了suspend的前面,导致resume方法未起效。
当我们用LockSupport改写上述程序,虽然我们仍然无法保证unpark发生在park前,但程序将可以正常运转退出。
这是因为LockSupport使用了类似信号量的机制,他为每一个线程准备了一个许可证,默认状况下许可证为false,park为消费许可证,而unpark为使许可证可用,但是与信号量不同,许可证不能累加
//改写后的程序
public class test {
    public static final Object u = new Object();
    public static class Change extends Thread {
        @Override
        public void run() {
            synchronized (u) {
                System.out.println("first");
                LockSupport.park();
            }
        }
    }
    public static void main(String[] args) {
        Change change = new Change();
        change.start();
        LockSupport.unpark(change);
    }
}

2.中断

与其他支持中断函数不同,LockSupport.park()方法不会抛出InterruptedException异常,但是可以从Thread.interrupted()获得中断标记。

八、限流算法

1.漏桶算法

基本思想
	利用一个缓存区,当有请求进入系统,无论请求速率如何,都先在缓存区中保存,然后以固定的流速流出缓存区
特点
	无论外部压力如何,漏桶算法总是以固定的流速处理数据

2.令牌桶算法

基本思想
	在令牌桶中,存放的将不是请求,而是令牌,处理程序只有拿到令牌,才能对请求进行处理,如果没有令牌,处理程序就要丢弃请求或者等待可用令牌,为了限制流速,该算法在每个单位时间内产生一定量的令牌存入桶中。
原文地址:https://www.cnblogs.com/INnoVationv2/p/13021135.html