线程(CountDownLatch+CyclicBarrier+Semaphore+ReentrantLock)

## 1. 乐观锁
juc 中的大部分类是通过无锁并发实现的(没有用synchronized)
CAS 机制  compare And swap 比较并交换
synchronized 可以称之为悲观锁
cas 体现的是乐观锁
    首先不会给共享资源加锁,而是做一个尝试
    先拿到旧值,查看旧值是否跟共享区域的值相等
        如果不等,那么说明别的线程改动了共享区域的值,我的修改失败
        如果相等,那么就让我的修改成功
    如果修改失败,重新尝试
```java
    int var5;
       // 修改失败,没关系,重新尝试 自旋
        do {
           // 获取共享区域的最新值
            var5 = this.getIntVolatile(var1, var2); // 10
                    // 比较并交换                                      最新值   最新值+1
        } while(! this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
```

### 1. 重入锁  ReentrantLock
.lock() 加锁
.unlock() 解锁
例子:
```java
static int i = 0;

public static void main(String[] args) throws InterruptedException {
    ReentrantLock rl = new ReentrantLock();

    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            try {
                rl.lock(); // 加锁
                i++;
            } finally {
                rl.unlock(); // 保证解锁一定被执行
            }
        }
    });

    Thread t2 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            try {
                rl.lock(); // 加锁
                i--;
            } finally {
                rl.unlock(); // 保证解锁一定被执行
            }
        }
    });

    t1.start();
    t2.start();
    t1.join();
    t2.join();
    //join确保主程不先执行结束 不然会直接输出i的值
    System.out.println(i);
}
```
synchronized 性能上比较 ReentrantLock 在高并发下低,ReentrantLock的内存占用会高一些

### 2. CountDownLatch
countdown 倒计时

当希望多个线程执行完毕后,再接着做下一步操作时,
例子:
```java
public static void main(String[] args) throws InterruptedException {
    CountDownLatch cdl = new CountDownLatch(3);// 构造方法需要指定倒计时的数字
    new Thread(()->{
        System.out.println("线程1开始运行"+new Date());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1准备完成"+new Date());
        cdl.countDown();//倒计时减一
    }).start();
    new Thread(()->{
        System.out.println("线程2开始运行"+new Date());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程2准备完成"+new Date());
        cdl.countDown();
    }).start();
    new Thread(()->{
        System.out.println("线程3开始运行"+new Date());
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程3准备完成"+new Date());
        cdl.countDown();
    }).start();

    // 主线程等待,直到倒计时为0
    System.out.println("主线程等待");
    cdl.await();
    System.out.println("ready go....");
}
```
一个应用例子:模拟10个玩家加载进度
```java
public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(10);
    String[] all = new String[10];//创建字符串数组

    for (int j = 0; j < 10; j++) {
        int x = j;
        new Thread(()->{
            Random r = new Random();//产生随机数
            for (int i = 0; i <= 100; i++) {
                try {
                    Thread.sleep(r.nextInt(100));//100以内的随机数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (all){
                    all[x]=(i+"%");
                    System.out.print("\r"+Arrays.toString(all));
                }//\r  为了使新值在原地输出
            }
            latch.countDown();

        }).start();
    }

    latch.await();//等待每个线程结束后执行下一句
    System.out.println("\nend...");
}
```

### 3. 循环栅栏
```java
// CyclicBarrier   可循环的 屏障(栅栏)
// 当满足CyclicBarrier设置的线程个数时,继续执行,没有满足则等待
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行

new Thread(()->{
    System.out.println("线程1开始.."+new Date());//打印现在的时间
    try {
        cb.await(); // 当个数不足时,等待
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println("线程1继续向下运行..."+new Date());
}).start();

new Thread(()->{
    System.out.println("线程2开始.."+new Date());
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        cb.await(); // 2 秒后,线程个数够2,继续运行
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    System.out.println("线程2继续向下运行..."+new Date());
}).start();
```
与倒计时锁的区别:倒计时锁只能使用一次,倒计时结束这个对象就没用了。
而循环栅栏可以重复利用。

### 4. 信号量
```java
Semaphore s = new Semaphore(3); // 限制了能同时运行的线程上限
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            s.acquire(); // 获得此信号量
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            s.release(); // 释放信号量
        }

    }).start();
}
```

猜你喜欢

转载自blog.csdn.net/qq_42736375/article/details/81748504