ReadWriteLock(读写锁)
通过查看文档可以知道它的实现类只有一个:ReentrantReadWriteLock(重入可读写锁!)
模拟一个缓存的例子:
/**
* 自定义一个缓存!
*/
class Mychche {
private volatile Map<String, Object> map = new HashMap<>();
//存 写;这里保证只有一个线程可以存
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
//取 读; 这里可以有多个线程读!
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
}
}
主启动类代码:
public class ReadWriteLockDemo {
public static void main(String[] args) {
Mychche mychche = new Mychche();
//写入
for (int i = 1; i < 5; i++) {
final int temp = i;
new Thread(() -> {
mychche.put(temp + "", temp + "");
}, String.valueOf(i)).start();
//读取
//写入
for (int j = 1; j < 5; j++) {
final int t = j;
new Thread(() -> {
mychche.get(t+"");
}, String.valueOf(j)).start();
}
}
}
}
通过运行上面的代码;会产生下面的结果(结果不唯一)
从上面的结果不难看出,这种方法是存在问题的,比如2的写入应该是在2的读取OK之后,同时写入之间可能会发生插入,比如:写入1,但是1还没写入ok的时候又写入2等等问题!
为了解决上面的问题;就可以使用读写锁!改变的代码如下:
/**
* 自定义一个缓存!
*/
class MychcheLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁:更加细粒度的控制
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存 写;这里希望只有一个线程可以存/写
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取 读; 这里希望可以有多个线程读!
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
可以看到写的时候只有一个线程写!但是读取的时候可以有多个线程读取!
注意:读写锁其中的写锁,又叫做独占锁:一次只能被一个线程占有;读锁又叫做共享锁,多个线程可以同时占有!
阻塞队列(BlockQueue)
阻塞:不得不阻塞的情况有两种:在写入时以及读取时
队列:先进先出(FIFO)
阻塞队列:
什么情况下使用阻塞队列
多线程下,A线程要调用B线程的情况,那么B必须先执行,如果B没有执行完,那么A线程会挂起!还有就是线程池也会用到!
四组API
第一组API,会抛出异常的:一言不合就开干
添加元素:add(e):
当队列未满的时候,向队列中添加元素正常;当队列满的时候,再向队列中添加元素的话,会抛出throw new IllegalStateException(“Queue full”);异常。
代码实现:
public class Test {
public static void main(String[] args) {
test1();
}
/**
* 抛出异常
*/
public static void test1(){
//参数为队列的大小;
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
}
}
当我们添加的数量等于小于队列的大小时;输出
但是当添加的数量大于队列时,会出现异常:无效状态异常:队列空了
删除元素:remove()
当队列不为空的时候,调用该方法,返回被移除的元素;当队列为空的时候在调用该方法,会抛出异常。
System.out.println(bq.remove());
System.out.println(bq.remove());
System.out.println(bq.remove());
但是多写一个移除的话,会出现下面的异常:没有元素
第二组:带有返回值的,不会抛出异常:为人处事会圆滑了
添加元素:offer(e)
需要主要:这里的offer方法只有一个参数,这个和我们后面讲解的一组的区别
当队列未满的时候,向队列中添加元素,返回true;当队列已经满了,继续向队列中添加元素的话,不会抛出异常,会返回false.
public static void test2(){
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
System.out.println(bq.offer("a"));
System.out.println(bq.offer("b"));
System.out.println(bq.offer("c"));
System.out.println(bq.offer("d"));
}
此时不会抛出异常:会返回一个布尔值
删除元素:poll()
注意:参数为空哦!
当队列不为空的时候,返回被移除的元素,当队列为空的时候,返回null.而不是抛出异常。
第三组:阻塞,一直等待:三十而立,咬定青山不放松
添加元素:put(e)
当队列满的时候,进入阻塞等待状态,一直等待,直到可以添加到队列中为止。
需要说明:在阻塞等待过程中,有可能会被中断,所以会抛出中断异常:throws InterruptedException。
public static void test3() throws InterruptedException {
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
bq.put("a");
bq.put("b");
bq.put("c");
bq.put("d");
}
可以发现不会异常,会一直等待!
删除元素:take()
当队列不为空的时候,返回被移除的元素;当队列为空的时候,进入阻塞等待状态。
public static void test3() throws InterruptedException {
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
bq.put("a");
bq.put("b");
bq.put("c");
System.out.println(bq.take());
System.out.println(bq.take());
System.out.println(bq.take());
System.out.println(bq.take());
}
因为这里只添加了三个,但是移除了4个,所以最后一个的时候,会一直等待:
第四组:带有等待超时的阻塞API
添加元素:offer(e,time,unit)
参数说明:
e:将要被添加到队列中的元素
time:long类型的。预设定的需要等待的时间
unit:TimeUnit.超时时间的单位
public static void test4() throws InterruptedException {
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
bq.offer("a");
bq.offer("b");
bq.offer("c");
bq.offer("d",2,TimeUnit.SECONDS);//前面是满的,等待超过2s就退出!
}
删除元素:poll(time,unit)
当队列为空的时候,进入阻塞等待,等到超时时间的时候,返回null.退出等待。
除了上面的添加和删除,还有一个获取队首元素!
总结四组API如下:
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer() | put() | offer(e,time,unit) |
移除 | remove | poll() | take() | poll(time,unit) |
检测队首元素 | element | peek | - | - |
同步队列(SynchronousQueue)
该队列进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
出入方法:put
移除方法:take
同步队列
- 和其他的BlockingQueue 不一样 SynchronousQueue 不存储元素
- put了一个元素,必须从里面先take取出来,否则不能再put进去值
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);//等待3秒
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);//等待3秒
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);//等待3秒
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}