模型是什么?
- 在学习并发编程的思想时,我们不能一上来就是条条框框书写的并发知识,相反,我们需要引进来这样的问题,供大家思考。
生产者与消费者,对产品必须保持正确的逻辑。
例如:产品为0,无法消费,只能生产
再如:仓库已满,无法生产,只能消费
- 上面的问题就是我们所说的关于生产者和消费者之间的关系,要保持这样的关系,必须确保操作的安全性,也就是我们的代码块需要加入锁这种概念。
- 该模式是一种数据操作的思想,为后面的编程打下坚实的基础。
用什么锁?
synchronized锁
- 但凡提到锁这个概念,脑子里不止一遍地蹦出synchronized关键字,当然这样的锁是JVM对其进行操作,我们无需关系何时加锁,何时解锁。只需要将锁的位置选好即可。
- 所以这里先使用
synchronized
进行模型展示:
public class Main{
public static void main(String[] args){
Data data = new Data();
new Thread(()->{
data.increment();
}).start();
new Thread(()->{
data.decrement();
}).start();
}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
//num作为标志,以此来解决
private int num = 0;
//加1操作
public synchronized void increment(){
while(num!=0){
this.wait();
}
num = 1;
System.out.println(Thread.currentThread().getName());
this.notify();
}
//减1操作
public synchronized void decrement(){
while(num==0){
this.wait();
}
num = 0;
System.out.println(Thread.currentThread().getName());
this.notify();
}
}
- 以上为使用synchronized进行加锁的过程。同样的我们可以使用Lock进行加锁。
Lock锁
- 使用Lock锁同样能够对生产者与消费者这样的模型进行设计,只需要将相关进行改变即可。
- 如上图所示:
- 我们只是将阻塞和唤醒的方式改变了一下,其它并没有改变。
public class Main{
public static void main(String[] args){
Data data = new Data();
new Thread(()->{
data.increment();
}).start();
new Thread(()->{
data.decrement();
}).start();
}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
//num作为标志,以此来解决
private int num = 0;
//加1操作
private Lock lock = new ReenTrantLock();
private Condition condition = lock.newCondition();
public void increment(){
lock.lock();
try{
while(num!=0){
condition.await();
}
num = 1;
System.out.println(Thread.currentThread().getName());
condition.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//减1操作
public synchronized void decrement(){
lock.lock();
try{
while(num==0){
condition.await();
}
num = 0;
System.out.println(Thread.currentThread().getName());
condition.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
- 这样我们就使用Lock对代码块进行了加锁。原理就不过多讲解。
那么问题来了
- 假如我们的生产者和消费者并非只有一个,也就是有多个消费者,多个生产者。这个时候,我们需要生产与消费的步骤可控,也就是指定生产,指定消费。
- 这个时候我们可以使用Lock锁的condition来指定线程,并对其进行阻塞或者唤醒,也就是有指定目标的唤醒。
public class Main{
public static void main(String[] args){
Data data = new Data();
new Thread(()->{
data.increment();
}).start();
new Thread(()->{
data.decrement();
}).start();
}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
//num作为标志,以此来解决
private int num = 0;
//加1操作
private Lock lock = new ReenTrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private Condition condition4 = lock.newCondition();
//第一个生产者
public void increment1(){
lock.lock();
try{
while(num!=0){
condition1.await();
}
num = 1;
System.out.println(Thread.currentThread().getName());
condition2.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//第二个生产者
public void increment2(){
lock.lock();
try{
while(num!=0){
condition3.await();
}
num = 1;
System.out.println(Thread.currentThread().getName());
condition4.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//第一个消费者
public synchronized void decrement1(){
lock.lock();
try{
while(num==0){
condition2.await();
}
num = 0;
System.out.println(Thread.currentThread().getName());
condition3.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//第二个消费者
public synchronized void decrement2(){
lock.lock();
try{
while(num==0){
condition4.await();
}
num = 0;
System.out.println(Thread.currentThread().getName());
condition1.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
- 上面我们创建了四个condition,分别控制两个生产者模型和两个消费者模型。
- 运行的逻辑为:
- 1、先使用condition1锁住生产者1代码块,结束后唤醒线程2,也就是唤醒消费者1模型
- 2、唤醒消费者1后,condition2锁住消费者1代码块,结束后唤醒线程3,也就是生产者2
- 3、唤醒生产者2后,condition3锁住生产者2代码块,结束后唤醒线程4,也就是消费者2
- 4、消费者2被唤醒后,condition4锁住消费者2代码块,结束后唤醒线程1,也就是生产者1.
- 上面的唤醒一圈后,又回到了原点。
其它锁辅助
- 提到锁辅助,很多人可能都觉得很疑惑,锁辅助器有很多,下面主要讲解以下三个并发包下的类:
CountDownLatch
- 一般称其为“计数器”,也就是我们常说的倒计时器,主要作用就是对我们的线程运行计时。多说无益,直接上代码:
public class Main{
public static void main(String[] args) throws InterruptedException{
CountDownLatch count = new CountDownLatch(6);
for(int i=1;i<=6;i++){
new Thread(()->{
count.countDown();
System.out.println(Thread.currentThread().getName());
},String.valueOf(i)).start();
}
count.await();
System.out.println("模型结束");
}
}
CyclicBarrier
- 既然有减一的操作,必定有加一的操作,CyclicBarrier作为一个类,它的作用为:
开发者文档:
允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
- 它的作用就是一道屏障,为我们的数据安全作出极大的贡献。具体代码就不不过多展示。
Semaphore
- 一个技术信息量,通过acquire阻塞线程,获取信息,然后通过release释放锁,等操作。
- 它的主要作用是限制线程的数量,控制内存运行等安全。
结语
- 本次学习还需加深理解,具体的实现也已经弄明白,希望再接再厉。