JUC并发编程
1.synchonized
锁方法的话,锁的对象是方法的调用者。
锁方法:
public synchronized void sale() {}
锁方法快:
synchronized(this) {}
/
synchronized(Tick.class) {}
锁方法:
package old;
public class SynchronizedTest {
public static void main(String[] args) {
//并发,多个线程操作同一个支援类
Tick tick = new Tick();
new Thread(() -> {
for (int i = 1; i < 50; i++) {
tick.sale();
}
}, "喜羊羊").start();
new Thread(() -> {
for (int i = 1; i < 50; i++) {
tick.sale();
}
}, "美羊羊").start();
new Thread(() -> {
for (int i = 1; i < 50; i++) {
tick.sale();
}
}, "懒羊羊").start();
}
}
//票类
class Tick {
private int tick = 50;
public synchronized void sale() {
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + "抢到了第:" + tick + "票,还剩余:" + (tick--) + "张票");
}
}
}
结果:
2.Lock
分为可重入锁,可重入读锁,可重入写锁
-
ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock
可重入锁
公平锁:十分公平,可以先来后到 3h 3s
非公平锁:十分不公平,可以先来后到(默认)
package old;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
//并发,多个线程操作同一个资源类
Tick2 tick = new Tick2();
new Thread(() -> {for (int i = 1; i < 50; i++) tick.sale();}, "喜羊羊").start();
new Thread(() -> {for (int i = 1; i < 50; i++) tick.sale();}, "美羊羊").start();
new Thread(() -> {for (int i = 1; i < 50; i++) tick.sale();}, "懒羊羊").start();
}
}
/**
* Lock三部曲
* 1.new ReentrantLock
* 2.lock.lock();
*3.finally--> {lock.unlock();} 解锁
*/
//票类
class Tick2 {
private int tick = 50;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + "抢到了第:" + tick + "票,还剩余:" + (tick--) + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.synchonized与lock的区别
4.synchonized(等待,通信,生产者消费者问题)
this.wait();
this.notify();
this.notifyAll();
package example;
/**
* 线程间的通信问题:生产者与消费者问题,等待唤醒,通知唤醒
* A :num++
* B :num--
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
/**
* 判断等待,业务,通知
*/
class Data { //数据 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number != 0) {
//等待
this.wait();
}
//业务
number++;
System.out.println(Thread.currentThread().getName() + "生产了,剩余数量为" + number);
//通知
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
//等待
this.wait();
}
//业务
number--;
System.out.println(Thread.currentThread().getName() + "消费了,数量为" + number);
//通知
this.notifyAll();
}
}
问题存在,ABCD四个线程
改为while就好了,解释一下,就是我们的if判断的话,判断一次,if的条件成立就进去循环了。而用while的话,while的条件一修改就会等待
package example;
/**
* 线程间的通信问题:生产者与消费者问题,等待唤醒,通知唤醒
* A :num++
* B :num--
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
/**
* 判断等待,业务,通知
*/
class Data { //数据 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while (number != 0) {
//等待
this.wait();
}
//业务
number++;
System.out.println(Thread.currentThread().getName() + "生产了,剩余数量为" + number);
//通知
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
//等待
this.wait();
}
//业务
number--;
System.out.println(Thread.currentThread().getName() + "消费了,数量为" + number);
//通知
this.notifyAll();
}
}
结果:
5.Lock(等待,通信,生产者消费者问题)
condition.await();
condition.signal();
condition.signalAll();
package example;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间的通信问题:生产者与消费者问题,等待唤醒,通知唤醒
* A :num++
* B :num--
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "D").start();
}
}
/**
* 判断等待,业务,通知
*/
class Data2 { //数据 资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment() {
lock.lock();
try {
while (number != 0) {
//等待
condition.await();
}
//业务
number++;
System.out.println(Thread.currentThread().getName() + "生产了,剩余数量为" + number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() {
lock.lock();
try {
while (number == 0) {
//等待
condition.await();
}
//业务
number--;
System.out.println(Thread.currentThread().getName() + "消费了,数量为" + number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果:
精准唤醒:
package example;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间的通信问题:生产者与消费者问题,等待唤醒,通知唤醒
* A :num++
* B :num--
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printfA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printfB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printfC();
}
}, "C").start();
}
}
/**
* 判断等待,业务,通知
*/
class Data3 { //数据 资源类
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void printfA() {
lock.lock();
try {
while (number != 1) {
//等待
condition1.await();
}
//业务
number = 2;
System.out.println(Thread.currentThread().getName() + "AAAAAAAAA");
//通知
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printfB() {
lock.lock();
try {
while (number != 2) {
//等待
condition2.await();
}
//业务
number = 3;
System.out.println(Thread.currentThread().getName() + "BBBBBBBB");
//通知
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printfC() {
lock.lock();
try {
while (number != 3) {
//等待
condition3.await();
}
//业务
number=1;
System.out.println(Thread.currentThread().getName() + "CCCCCCCCC");
//通知
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果:
6.8锁问题
/**
* 8锁,就是关于锁的8个问题
* 1、标准情况下,两个线程先打印 发短信还是 打电话?1/发短信 2/打电话
* 1、sendSms延迟4秒,两个线程先打印 发短信还是 打电话?1/发短信 2/打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者!、
// 两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
/**
* 3、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
* 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
// 这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
*/
public class Test3 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone3{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
/**
* 1、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
* 2、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
*/
public class Test4 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone4唯一的一个 Class 对象
class Phone4{
// 静态的同步方法 锁的是 Class 类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通的同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
7.集合不安全
List不安全
我们来看一下List这个集合类:
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
List<Object> arrayList = new ArrayList<>();
for(int i=1;i<=10;i++){
new Thread(()->{
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
},String.valueOf(i)).start();
}
}
}
会造成:
ArrayList 在并发情况下是不安全的!
解决方案:
1、切换成Vector就是线程安全的啦!
2、使用Collections.synchronizedList(new ArrayList<>());
public class ListTest {
public static void main(String[] args) {
List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());
for(int i=1;i<=10;i++){
new Thread(()->{
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
},String.valueOf(i)).start();
}
}
}
3、使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();
public class ListTest {
public static void main(String[] args) {
List<Object> arrayList = new CopyOnWriteArrayList<>();
for(int i=1;i<=10;i++){
new Thread(()->{
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;
CopyOnWriteArrayList比Vector厉害在哪里?
Vector底层是使用synchronized关键字来实现的:效率特别低下。
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
Set不安全
和List、Set同级的还有一个BlockingQueue 阻塞队列;
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
- 使用Collections工具类的synchronized包装的Set类
- 使用CopyOnWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest {
public static void main(String[] args) {
// Set<String> hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
Set<String> hashSet = new CopyOnWriteArraySet<>();//解决方案2
for (int i = 1; i < 100; i++) {
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
HashSet底层是什么?
hashSet底层就是一个HashMap;
他把value放到了hashmap的key,把hashmap的value用常量代替了
public HashSet() {
map = new HashMap<>();
}
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
Map不安全
回顾map的基本操作:
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
同样的HashMap基础类也存在并发修改异常!
public static void main(String[] args) {
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
for (int i = 1; i < 100; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
结果同样的出现了:异常java.util.ConcurrentModificationException 并发修改异常
解决方案:
- 使用Collections.synchronizedMap(new HashMap<>());处理;
- 使用ConcurrentHashMap进行并发处理
底层原理:CAS+synchonized,就是比较并替换加同步锁
8.Callable(简单)
1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()
代码测试
传统使用线程方式:
public class CallableTest {
public static void main(String[] args) {
for (int i = 1; i < 10; i++) {
new Thread(new MyThread()).start();
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
使用Callable进行多线程操作:
Calleable 泛型T就是call运行方法的返回值类型;
但是如何使用呢?
Callable怎么放入到Thread里面呢?
对于Thread运行,只能传入Runnable类型的参数;
我们这是Callable 怎么办呢?
看JDK api文档:
在Runnable里面有一个叫做FutureTask的实现类,我们进去看一下。
FutureTask中可以接受Callable参数;
这样我们就可以先把Callable 放入到FutureTask中, 如何再把FutureTask 放入到Thread就可以了。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
for (int i = 1; i < 10; i++) {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<>( Callable)).start();
MyThread thread= new MyThread();
//适配类:FutureTask
FutureTask<String> futureTask = new FutureTask<>(thread);
//放入Thread使用
new Thread(futureTask,String.valueOf(i)).start();
//获取返回值
String s = futureTask.get();
System.out.println("返回值:"+ s);
}
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Call:"+Thread.currentThread().getName());
return "String"+Thread.currentThread().getName();
}
}
这样我们就可以使用Callable来进行多线程编程了,并且我们发现可以有返回值,并且可以抛出异常。
注意两个重点:
//结果会被缓存,只进行其中一次线程操作,返回一个结果
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start();
String s = futureTask.get();
//线程运行可能有耗时间操作,获取返回结果可能会很久,我们尽量最后在获取返回结果,或者使用异步通信
9.常用的辅助类(必会!)
CountDownLatch
其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!
//这是一个计数器 减法
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); //每个线程都数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零 然后向下执行
System.out.println("close door");
}
}
主要方法:
- countDown 减一操作;
- await 等待计数器归零。
await等待计数器为0,就唤醒,再继续向下运行。
CyclickBarrier
其实就是一个加法计数器;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙~");
});
for (int i = 1; i <= 7; i++) {
//子线程
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
try {
cyclicBarrier.await(); //加法计数 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
Semaphore:信号量
抢车位:
3个车位 6辆车:
public class SemaphoreDemo {
public static void main(String[] args) {
//停车位为3个
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
int finalI = i;
new Thread(()->{
try {
semaphore.acquire(); //得到
//抢到车位
System.out.println(Thread.currentThread().getName()+" 抢到了车位{"+ finalI +"}");
TimeUnit.SECONDS.sleep(2); //停车2s
System.out.println(Thread.currentThread().getName()+" 离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
10.读写锁
先对于不加锁的情况:
如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;
我们采用五个线程去写入,使用十个线程去读取。
我们来看一下这个的效果,如果我们不加锁的情况!
package com.ogj.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCache_ReadWriteLock{
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//写入
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
}
public String get(String key){
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
String o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
return o;
}
}
结果:
Thread-0 线程 开始写入
Thread-4 线程 开始写入 # 插入了其他的线程进行写入
Thread-4 线程 写入OK
Thread-3 线程 开始写入
Thread-1 线程 开始写入
Thread-2 线程 开始写入
Thread-1 线程 写入OK
Thread-3 线程 写入OK
Thread-0 线程 写入OK # 对于这种情况会出现 数据不一致等情况
Thread-2 线程 写入OK
Thread-5 线程 开始读取
Thread-6 线程 开始读取
Thread-6 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-5 线程 读取OK
Thread-8 线程 开始读取
Thread-8 线程 读取OK
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-11 线程 开始读取
Thread-12 线程 开始读取
Thread-12 线程 读取OK
Thread-10 线程 读取OK
Thread-14 线程 开始读取
Thread-13 线程 开始读取
Thread-13 线程 读取OK
Thread-11 线程 读取OK
Thread-14 线程 读取OK
Process finished with exit code 0
所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证
package com.ogj.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCache_ReadWriteLock{
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//加锁
readWriteLock.writeLock().lock();
try {
//写入
//业务流程
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); //解锁
}
}
public String get(String key){
//加锁
String o="";
readWriteLock.readLock().lock();
try {
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
运行结果如下:
Thread-0 线程 开始写入
Thread-0 线程 写入OK
Thread-1 线程 开始写入
Thread-1 线程 写入OK
Thread-2 线程 开始写入
Thread-2 线程 写入OK
Thread-3 线程 开始写入
Thread-3 线程 写入OK
Thread-4 线程 开始写入
Thread-4 线程 写入OK
# 以上 整个过程没有再出现错乱的情况,对于读取,我们运行多个线程同时读取,
# 因为这样不会造成数据不一致问题,也能在一定程度上提高效率
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-5 线程 开始读取
Thread-11 线程 开始读取
Thread-11 线程 读取OK
Thread-10 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-6 线程 开始读取
Thread-5 线程 读取OK
Thread-14 线程 开始读取
Thread-8 线程 开始读取
Thread-14 线程 读取OK
Thread-6 线程 读取OK
Thread-13 线程 开始读取
Thread-12 线程 开始读取
Thread-13 线程 读取OK
Thread-8 线程 读取OK
Thread-12 线程 读取OK
11.阻塞队列
阻塞
队列
阻塞队列jdk1.8文档解释:
BlockingQueue
blockingQueue 是Collection的一个子类;
什么情况我们会使用 阻塞队列呢?
多线程并发处理、线程池!
整个阻塞队列的家族如下:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;
BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列
如何使用阻塞队列呢?
操作:添加、移除
但是实际我们要学的有:
四组API