JUC-01-JUC简介,进程和线程,Lock锁,生产消费问题

01,什么是JUC?

所谓JUC就是java并发工具包:java.util.concurrent

学习JUC主要学习三个包的使用:

  • java.util.concurrent
  • java.util.concurrent.atomic
  • java.util.concurrent.locks

02,回顾多线程

进程和线程

进程

  • 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)
  • 进程不依赖于线程而独立存在,一个进程中可以启动多个线程
  • Java默认有几个线程有2 个 ,mian、GC

线程:

  • 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。
  • 线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。

java开启多线程的两种方法

第一种:通过继承Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:

1、定义一个类继承Thread类,并重写Thread类的run()方法,
run()方法的方法体就是线程要完成的任务,因此把run()称为线程的行体

2、创建该类的实例对象,即创建了线程对象

3、调用线程对象的start()方法来启动线程

注意:也可以使用匿名内部类的方式创建线程
  • 一般的实现举例:
//使用这种方式不能达到资源共享的目的
public class Test {
    public static void main(String[] args) {
    	//第二步:
       MyThread myThread=new MyThread();
       //第三步:
       myThread.start();
    }
}
//第一步:
class MyThread extends  Thread{
    @Override
    public void run() {
        System.out.println("线程执行的内容。。。");
    }
}
  • 使用匿名内部类的方式实现
public class Test {
    public static void main(String[] args) {
        //方式一,使用匿名继承Thread
      new Thread("Thread_A") {
          @Override
          public void run() {
              System.out.println("Thread_A线程执行的内容。。。");
          }
      }.start();

      //方式二,使用lambda表达式
        new Thread(()->{
            System.out.println("Thread_B线程执行的内容。。。");
        },"Thread_B").start();
    }
}

第二种,通过实现Runnable接口创建线程类

这种方式创建并启动多线程的步骤如下:

1、定义一个类实现Runnable接口;

2、创建该类的实例对象obj;

3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;

4、调用线程对象的start()方法启动该线程;
//注意:使用这种方式可以达到资源共享的目的
public class Test {
    public static void main(String[] args) {
        //第二步:
        MyThread myThread=new MyThread();
        //第三步:
        Thread thread=new Thread(myThread,"Thread_A");
        //第四步:
        thread.start();

        //第三步和第四步的合并写法
        new Thread(myThread,"Thread_B").start();
    }
}
//第一步:
class MyThread implements  Runnable{
    @Override
    public void run() {
        System.out.println("线程执行的内容。。。");
    }
}

线程的状态:

  • Thread类中有一个state方法,在该方法中使用枚举的方式列出了一个线程有多少中状态
public enum State {
        NEW,			//新建
        RUNNABLE,		//运行
        BLOCKED,		//阻塞
        WAITING,		//等待
        TIMED_WAITING,	//超时等待
        TERMINATED;		//终止
    }

线程等待:wait和sleep的对比

1、来自不同的类:

  • wait是Object类中的方法
  • sleep是Thread类中的方法
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
但是它的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待
锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

2、关于锁被释放

  • wait会释放锁
  • sleep不会释放锁

3、使用的范围不同

  • wait必须在同步代码块中
  • sleep可以在任何地方休眠

03,java.util.concurrent.locks.Lock(Lock锁)

Synchronized

在说Lock锁之前,先说一下Synchronized关键字

  • 之后我们在使用多线程的时候,就不要再把资源放在线程类里例如下面这种:
public class Test001 {
    public static void main(String[] args) {
        TestThread testThread=new TestThread();
        testThread.start();
    }
}

class TestThread extends Thread{
    private int ticket=50;//资源
    @Override
    public void run() {
        if(ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+ticket);
        }
    }
}
  • 把资源和线程类分开,使用的时候直接把资源丢进线程中即可
  • 为了安全给sale方法解锁,使用synchronized关键字
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源,把资源直接丢入线程
        Ticket ticket=new Ticket();
        new Thread(()->{//每个线程都买60次
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"C").start();

    }
}
//资源类
class Ticket{
    //定义票数
    private int ticket=50;
    //synchronized 本质:排队
    public synchronized void sale(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+":sale:"+(ticket--)+" ticket,剩余:"+ticket);
        }
    }
}

Lock锁

  • Lock是java.util.concurrent.locks包下的一个接口,这个包下面一共三个接口
    在这里插入图片描述

  • Lock接口有三个实现类
    在这里插入图片描述

ReentrantLock :可重入锁

ReentrantReadWriteLock:可重入读写锁的两个内部类
- ReentrantReadWriteLock.ReadLock:可重入读写锁的读锁
- ReentrantReadWriteLock.WriteLock :可重入读写锁的写锁
  • 使用方法:我们使用Lock接口的一个实现类ReentrantLock
 使用Lock锁的三部曲:
1、 Lock lock=new ReentrantLock();
2、 lock.lock(); // 加锁
3、 在finally代码块中使用:lock.unlock();// 解锁
  • 示例:
public class SaleTiketDemo02 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源,把资源直接丢入线程
        Ticket2 ticket=new Ticket2();
        new Thread(()->{//每个线程都买60次
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<60;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类
class Ticket2{
    //定义票数
    private int ticket=50;

    //从底层源码可以看出,不加参数为非公平锁,加参数True为公平锁
    Lock lock=new ReentrantLock();//第一步

    public void sale(){
        lock.lock();//第二步:
        try {//业务代码
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+":sale:"+(ticket--)+" ticket,剩余:"+ticket);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • ReentrantLock :可重入锁

我们来看一下这个类的构造方法

//有两个构造方法
//第一个构造方法,不穿参数,默认是非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//第二个构造方法,传参数,true为公平锁,false为非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:有先来后到的说法,要排队
非公平锁:可以插队

Synchronized 和 Lock 区别

  • 1、Synchronized 内置的Java关键字, Lock 是一个Java接口
  • 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • 3、Synchronized 会自动释放锁(被修饰的类或者方法执行结束会自动释放),lock 必须要手动释放锁!如果不释放锁,死锁
  • 4、Synchronized 线程 1(获得锁之后阻塞)、线程2(等待,傻傻的等),Lock锁就不一定会等待下去
  • 5、Synchronized 可重入锁,不可以中断的,非公平
  • 6,Lock 可重入锁,可以判断锁,非公平还是公平(可以自己设置,参考ReentrantLock的构造方法
  • 7、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码

04,线程之间的通讯(生产者和消费者问题)

  • 生产者和消费者的问题,其实就是线程之间的通信问题
  • 线程之间的通信问题就是,等待唤醒机制

synchronized版本

在这里插入图片描述

  • 案例分析:
synchronized版本的生产消费问题:synchronized ,wait,notify

线程交替执行,线程A,B,C,D操作一个变量num(值在0-1之间)
* 线程A,C执行:num+1
* 线程B,D执行:num-1

四个线程:两个生产者线程(A和C),两个消费者线程(B和D)

问题:
当生产者线程A生产完之后,可能会唤醒生产者C,这时候num的值就有可能为2
当消费者线程B消费完之后,可能会唤醒消费者D,这时候num的值就有可能为-1

解决办法:
方法一:如果使用【if】的话,只会判断一次,可能还会出现上面的问题(虚假唤醒)
方法二:如果使用【循环】的话,把等待放在循环中,每次其他线程执行完,唤醒一个线程的时候都会进行判断

结论:
使用if会出现虚假唤醒解决不了上面的问题(上面列出的问题),使用while循环则可以

为何使用if不行的具体原因:
当调用wait()方法的时候,线程会放弃对象锁,线程A调用wait方法释放了对象锁,刚好唤醒了线程C,
此时线程C会执行加1操作,当线程C执行结束刚好又唤醒线程A,由于线程A已经执行完了if语句,
就不会再次判断,接着继续执行,从而出现上面的为2的情况,使用while则需要再次判断,可以避免上面的问题。
  • 实例代码:
public class test01 {
    public static void main(String[] args) throws InterruptedException{
        //并发:多个线程共享一个资源资源
        //创建多个线程,把资源直接丢入线程
        Data data=new Data();

        new Thread(()->{
            for(int i=0;i<10;i++){
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"A:producer").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"B:consumer").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"C:producer").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"D:consumer").start();


    }
}

/*等待,业务,通知*/
class Data{//数据类,资源类
    private int num=0;
    //+1
    public synchronized  void increment() throws InterruptedException{
        while(num!=0){
            this.wait();//等待
        }
        num++;//业务
        System.out.println(Thread.currentThread().getName()+"==>"+num);
        this.notifyAll();//通知
    }
    //-1
    public synchronized  void decrement()throws InterruptedException{
        while(num==0){
            this.wait();//等待
        }
        num--;//业务
        System.out.println(Thread.currentThread().getName()+"==>"+num);
        this.notifyAll();//通知
    }
}
  • 虚假唤醒是如何产生的
while (num != 0) {}

换成 if (num == 0) {}

就会出现虚假唤醒。官方文档有标注;
  • 为什么if判断会出现虚假唤醒?
1. 因为if只会判断一次

2.while每次都会判断

JUC 版本

在这里插入图片描述

  • 案例分析:
JUC 版本的生产消费问题:Lock,await,single

Lock:代替synchronized关键字的作用 

通过lock.newCondition创建一个条件

condition.await:代替wait

condition.single:代替notify

通过刚才的演示可以发现,这种实现的效果和使用synchronized,wait,notify的效果是一样的

注意:里面也要使用while进行判断

任何一种新的技术绝不会只是对原来技术的一种覆盖,肯定有它的优势,和对原来技术的补充,
public class Test02 {
    public static void main(String[] args) throws InterruptedException{
        //并发:多个线程共享一个资源资源
        //创建多个线程,把资源直接丢入线程
        Data2 data=new Data2();
        new Thread(()->{
            for(int i=0;i<15;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"A:producer").start();

        new Thread(()->{
            for(int i=0;i<15;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"B:consumer").start();

        new Thread(()->{
            for(int i=0;i<15;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"C:producer").start();

        new Thread(()->{
            for(int i=0;i<15;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"D:consumer").start();


    }
}

/*等待,业务,通知*/
class Data2{ //数据类,资源类
    private int num=0;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();

    //+1
    public void increment() throws InterruptedException{
        try {
            lock.lock();
            while(num!=0) {
                condition.await();//等待
            }
            num++;//业务
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            condition.signalAll();//通知
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    //-1
    public synchronized  void decrement()throws InterruptedException{
        try {
            lock.lock();
            while(num==0){
               condition.await();//等待
            }
            num--;//业务
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            condition.signalAll();//通知
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

java.util.concurrent.locks.Condition(精准唤醒)

  • java.util.concurrent.locks一共有三个接口
Lock
Condition
ReadWriteLock 
  • 示例:
//Condition:实现精准的通知和唤醒作用

/*A执行完调用B
* B执行完调用C
* C执行完调用A
* */
public class Test03 {
    public static void main(String[] args) throws InterruptedException{
        //并发:多个线程共享一个资源资源
        //创建多个线程,把资源直接丢入线程
        Data3 data=new Data3();
        new Thread(()->{
            for(int i=0;i<12;i++){
                try {
                    data.printA();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }},"Thread_A").start();

        new Thread(()->{
            for(int i=0;i<12;i++){
                try {
                    data.printB();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }},"Thread_B").start();

        new Thread(()->{
            for(int i=0;i<12;i++){
                try {
                    data.printC();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }},"Thread_C").start();

    }
}

/*等待,业务,通知*/
class Data3{ //数据类,资源类
    private int flag=1;//flag为1 A线程执行,2 B 线程执行,3 C 线程执行
    Lock lock=new ReentrantLock();
    Condition conditionA=lock.newCondition();
    Condition conditionB=lock.newCondition();
    Condition conditionC=lock.newCondition();

    public void printA(){
        try {
            lock.lock();
            while (flag!=1){
                conditionA.await();//等待
            }
            System.out.println(Thread.currentThread().getName()+".....AAAAA....");
            flag=2;//唤醒B线程
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        try{
            lock.lock();
            while (flag!=2){
                conditionB.await();//等待
                }
            System.out.println(Thread.currentThread().getName()+".....BBBBB....");
            flag=3;//唤醒B线程
        conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        try {
            lock.lock();
            while (flag!=3){
                conditionC.await();//等待
            }
            System.out.println(Thread.currentThread().getName()+".....CCCCC....");
            flag=1;//唤醒B线程
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值