1. 线程(续)
juc 中的大部分类是通过无锁并发实现的(没有用synchronized)
CAS 机制 compare And swap 比较并交换
synchronized 可以称之为悲观锁
cas 体现的是乐观锁
首先不会给共享资源加锁,而是做一个尝试
先拿到旧值,查看旧值是否跟共享区域的值相等
如果不等,那么说明别的线程改动了共享区域的值,我的修改失败
如果相等,那么就让我的修改成功
如果修改失败,没关系,重新尝试
java 10行
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 34行
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();
System.out.println(i);
}
synchronized 性能上比较 ReentrantLock 在高并发下低,ReentrantLock的内存占用会高一些
2. 倒计时CountDownLatch
当希望多个线程都执行完毕后,再接着做下一步操作时,
例子:
java 39行
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 28行
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));
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (all){
all[x]=(i+"%");
System.out.print("\r"+Arrays.toString(all));
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("\nend...");
}
3. 循环栅栏 CyclicBarrier
java 33行
// 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. 信号量 Semaphore
java 20行
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();