并发包(java.util.concurrent)
为了更好的支持并发程序,jdk内部提供了大量实用的API和框架
同步控制(锁)
锁(lock)可以完全代替关键字synchronize.
jdk中锁是一个接口,提供了三个实现类,读锁,写锁,重入锁.
读写锁
读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。简单的说就是写锁是排他锁,读锁是共享锁。
下面代码开启了10个读取线程,10个写线程
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " 开始读取");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 读取完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write() {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " 开始写数据");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 写数据完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockTest rwlt = new ReadWriteLockTest();
for(int i = 0 ;i < 10 ; i ++){
new Thread() {
@Override
public void run() {
rwlt.read();
}
}.start();
new Thread() {
@Override
public void run() {
rwlt.write();
}
}.start();
}
}
}
打印结果
Thread-0 开始读取
Thread-0 读取完毕
Thread-1 开始写数据
Thread-1 写数据完毕
Thread-3 开始写数据
Thread-3 写数据完毕
Thread-2 开始读取
Thread-4 开始读取
Thread-2 读取完毕
Thread-4 读取完毕
Thread-5 开始写数据
Thread-5 写数据完毕
Thread-6 开始读取
Thread-6 读取完毕
Thread-7 开始写数据
Thread-7 写数据完毕
Thread-8 开始读取
Thread-8 读取完毕
Thread-9 开始写数据
Thread-9 写数据完毕
Thread-10 开始读取
Thread-10 读取完毕
Thread-11 开始写数据
Thread-11 写数据完毕
Thread-12 开始读取
Thread-12 读取完毕
Thread-13 开始写数据
Thread-13 写数据完毕
Thread-14 开始读取
Thread-14 读取完毕
Thread-15 开始写数据
Thread-15 写数据完毕
Thread-16 开始读取
Thread-16 读取完毕
Thread-17 开始写数据
Thread-17 写数据完毕
Thread-18 开始读取
Thread-18 读取完毕
Thread-19 开始写数据
Thread-19 写数据完毕
重入锁
可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。
//打印类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Print {
private Lock lock = new ReentrantLock();
public void prt(){
lock.lock();
for(int i = 0 ; i < 5 ; i++){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
lock.unlock();
}
}
//线程类
public class Th1 implements Runnable{
private Print p;
public Th1(Print p) {
this.p = p;
}
@Override
public void run() {
p.prt();
}
}
//Main方法类
public class M {
public static void main(String[] args) {
Print p = new Print();
new Thread(new Th1(p)).start();
new Thread(new Th1(p)).start();
}
}
在重入锁中提供了公平锁和非公平锁 构造的时候传入参数 true/false 来区分
公平锁与非公平锁
公平表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序。而非公平就是一种获取锁的抢占机制,和公平相对就是先来不一定先得,这个方式可能造成某些线程饥饿(一直拿不到锁)
Condition(并发包中的wait与notify)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition 配合Lock 实现线程的等待 与通知
*/
public class ConditionTest{
public static ReentrantLock lock=new ReentrantLock();
public static Condition condition =lock.newCondition();
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
lock.lock();//请求锁
try{
System.out.println(Thread.currentThread().getName()+"==》进入等待");
condition.await();//设置当前线程进入等待
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//释放锁
}
System.out.println(Thread.currentThread().getName()+"==》继续执行");
}
}.start();
new Thread(){
@Override
public void run() {
lock.lock();//请求锁
try{
System.out.println(Thread.currentThread().getName()+"=》进入");
Thread.sleep(2000);//休息2秒
condition.signal();//随机唤醒等待队列中的一个线程
System.out.println(Thread.currentThread().getName()+"休息结束");
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//释放锁
}
}
}.start();
}
}
Semaphore(信号量)
信号量为多线程提供了更为强大的控制方法,无论是锁还是synchronize,一次都只允许一个线程访问一个资源,而信号量可以指定多少个线程,同时访问某一个资源.
通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可,
//打印类
import java.util.concurrent.Semaphore;
public class Print {
private Semaphore semaphore = new Semaphore(5);
public void prt(){
try {
System.out.println(Thread.currentThread().getName() + " : 准备进入" );
semaphore.acquire();
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " : 进入" );
Thread.sleep(3000);
semaphore.release();
System.out.println(Thread.currentThread().getName() + " : 离开" );
} catch (Exception e) {
e.printStackTrace();
}
}
}
//线程类
public class Th1 implements Runnable{
private Print p;
public Th1(Print p) {
this.p = p;
}
@Override
public void run() {
p.prt();
}
}
//Main方法类
public class M {
public static void main(String[] args) {
Print p = new Print();
for(int i = 0 ; i < 14 ; i++){
new Thread(new Th1(p)).start();
}
}
}
计数器(CountDownLatch)
主线程模拟裁判,八个子线程模拟运动员,裁判和八个运动员都就位以后,运动员才能开始跑步
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
final CountDownLatch cdl1 = new CountDownLatch(1);//裁判吹哨开始倒计时,归零运动员开始跑步
final CountDownLatch cdl2 = new CountDownLatch(8);//运动员跑完了,裁判公布结果。//cdl2.countDown() 这个方法 每次到达一个会减少1 而不是一次减完 这里八个运动员跑步 所以这里 实例化对象的 参数 应该是 8
final Map<String , Long> map = new HashMap<String , Long>();
for(int i =0 ; i < 8 ;i ++){//八个运动员
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("运动员 " + Thread.currentThread().getName() + " 就位");
try {
cdl1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运动员:" + Thread.currentThread().getName() + "开始跑步");
try {
Thread.sleep(new Random().nextInt(10) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(Thread.currentThread().getName(), new Date().getTime());
System.out.println("运动员:" + Thread.currentThread().getName() + " 跑完");
cdl2.countDown();
}
});
}
long l = 0;
try {
System.out.println("裁判就位");
Thread.sleep(3000);
cdl1.countDown();
System.out.println("裁判发了起跑消息,运动员起跑 , 裁判等待跑完");
l = new Date().getTime();
cdl2.await();
System.out.println("跑完了,裁判发布结果");
System.out.println("结果如下");
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdown();
for(Entry<String, Long> entry : map.entrySet()){
System.out.println(entry.getKey() + " 所用所用时间是 :"
+ (entry.getValue() - l));
}
}
}
运动员 pool-1-thread-1 就位
运动员 pool-1-thread-4 就位
运动员 pool-1-thread-3 就位
运动员 pool-1-thread-2 就位
裁判就位
运动员 pool-1-thread-6 就位
运动员 pool-1-thread-7 就位
运动员 pool-1-thread-5 就位
运动员 pool-1-thread-8 就位
裁判发了起跑消息,运动员起跑 , 裁判等待跑完
运动员:pool-1-thread-1开始跑步
运动员:pool-1-thread-3开始跑步
运动员:pool-1-thread-2开始跑步
运动员:pool-1-thread-4开始跑步
运动员:pool-1-thread-8开始跑步
运动员:pool-1-thread-5开始跑步
运动员:pool-1-thread-7开始跑步
运动员:pool-1-thread-6开始跑步
运动员:pool-1-thread-3 跑完
运动员:pool-1-thread-5 跑完
运动员:pool-1-thread-7 跑完
运动员:pool-1-thread-6 跑完
运动员:pool-1-thread-2 跑完
运动员:pool-1-thread-8 跑完
运动员:pool-1-thread-1 跑完
运动员:pool-1-thread-4 跑完
跑完了,裁判发布结果
结果如下
pool-1-thread-4 所用所用时间是 :7999
pool-1-thread-5 所用所用时间是 :2999
pool-1-thread-2 所用所用时间是 :5998
pool-1-thread-3 所用所用时间是 :998
pool-1-thread-1 所用所用时间是 :7998
pool-1-thread-7 所用所用时间是 :2999
pool-1-thread-6 所用所用时间是 :3999
pool-1-thread-8 所用所用时间是 :5999
CyclicBarrier(循环栅栏)
循环栅栏与计算器很像,但是可以反复使用,下面模拟 十个人一起去景点的场景.
//线程类
package c;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
public class Th1 implements Runnable{
private CyclicBarrier cb;
public Th1(CyclicBarrier cb2) {
this.cb = cb2;
}
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了黄鹤楼 已经有:" + (cb.getNumberWaiting() + 1) + "个到达黄鹤楼");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齐,下一站龟山");
}
cb.await();
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了龟山 已经有:" + (cb.getNumberWaiting() + 1) + "个到达龟山");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齐,下一站东湖");
}
cb.await();
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了东湖 已经有:" + (cb.getNumberWaiting() + 1) + "个到达东湖");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齐,结束");
}
cb.await();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
//Main方法类
package c;
import java.util.concurrent.CyclicBarrier;
public class M {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(10);
for(int i = 0 ; i < 10 ; i++){
new Thread(new Th1(cb)).start();
}
}
}
打印结果
Thread-3到了黄鹤楼 已经有:1个到达黄鹤楼
Thread-7到了黄鹤楼 已经有:2个到达黄鹤楼
Thread-8到了黄鹤楼 已经有:3个到达黄鹤楼
Thread-9到了黄鹤楼 已经有:4个到达黄鹤楼
Thread-1到了黄鹤楼 已经有:5个到达黄鹤楼
Thread-5到了黄鹤楼 已经有:6个到达黄鹤楼
Thread-2到了黄鹤楼 已经有:7个到达黄鹤楼
Thread-0到了黄鹤楼 已经有:7个到达黄鹤楼
Thread-6到了黄鹤楼 已经有:9个到达黄鹤楼
Thread-4到了黄鹤楼 已经有:10个到达黄鹤楼
Thread-6到了龟山 已经有:1个到达龟山
Thread-1到了龟山 已经有:1个到达龟山
Thread-5到了龟山 已经有:3个到达龟山
Thread-3到了龟山 已经有:4个到达龟山
Thread-8到了龟山 已经有:4个到达龟山
Thread-2到了龟山 已经有:6个到达龟山
Thread-4到了龟山 已经有:7个到达龟山
Thread-9到了龟山 已经有:8个到达龟山
Thread-7到了龟山 已经有:9个到达龟山
Thread-0到了龟山 已经有:10个到达龟山
Thread-3到了东湖 已经有:1个到达东湖
Thread-0到了东湖 已经有:2个到达东湖
Thread-8到了东湖 已经有:3个到达东湖
Thread-4到了东湖 已经有:4个到达东湖
Thread-2到了东湖 已经有:4个到达东湖
Thread-5到了东湖 已经有:4个到达东湖
Thread-6到了东湖 已经有:7个到达东湖
Thread-1到了东湖 已经有:7个到达东湖
Thread-9到了东湖 已经有:9个到达东湖
Thread-7到了东湖 已经有:10个到达东湖