01.JUC+锁的基本概念+生产者和消费者问题

1.环境

image-20200818160433880

2.JUC简介与线程基础

2.1.juc是什么?

主要指的就是java.util下面的包:java.util.concurrent

2.2.java默认两个线程

main和GC

2.3.java能否直接调用线程

java没办法调用线程,底层是通过调用c++来调用的

2.4.并发,并行

并发是交替的(多个线程操作同一资源,快速交替,可以充分利用cpu资源)

并行是同时的(多核情况下,真正的执行多个任务)

2.5.线程状态

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    //超时等待
    TIMED_WAITING,
    //终止
    TERMINATED;
}

2.6.slep和wait区别

  • wait来自Object基础类,sleep来自java.util.concurrent

  • 两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。

  • 两者都可以暂停线程的执⾏。

  • Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。

  • wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者
    notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long
    timeout)超时后线程会⾃动苏醒。

  • wait必须在同步代码块中,sleep可以作用任何地方

  • wait不需要捕获异常,sleep必须要捕获异常

3.Lock锁

3.1synchronized锁

主要两种写法:synchronized同步方法(锁住的是方法的调用对象)和synchronized代码块(代码块写法可以把类传进去)

package com.juc.demo01;

/**
 * Created by yj on 2020/8/18 21:07
 */
public class TestTicket {
    public static void main(String[] args){

        Ticket ticket = new Ticket();
        new Thread(()->{
            for(int i=0;i<50;i++){
                ticket.sale();
            }

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


}

class Ticket{
    //属性,方法
    private int number =50;

    //买票方式
    public synchronized void sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);
        }
    }
}

如果没加锁synchronized,就会出现如下情况,造成票被多个线程去抢购,这样就会线程不安全,而加锁后就类似于一个门锁,每次都只有一个线程去拿到锁然后进门里面,然后拿完票后就出去了,然后下一个线程拿到门锁进去,依次类推

image-20200818213841593r

3.2lock锁

java.util.concurrent.locks.Lock包下的Lock锁主要的实现类如下。

注意ReentrantLock是他的实现类

image-20200818215304634

上面写法改为lock锁来写

package com.juc.demo01;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by yj on 2020/8/18 21:07
 */
public class TestTicket02 {
    public static void main(String[] args){

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

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

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

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

    }


}

class Ticket2{
    //属性,方法
    private int number =50;
    Lock lock = new ReentrantLock();
    //买票方式
    public void sale(){

        try {
            //1.加锁
            lock.lock();
            //2.业务代码
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);
            }
        }catch (Exception e){
            e.printStackTrace();

        }finally {
            /3.解锁
            lock.unlock();
        }

    }
}

3.3synchronized和Lock(这儿笔记都是和ReentrantLock比较的)区别

  • synchronized是java内置关键字,Lock则是java.util.concurrent.locks.Lock的一个类(作为一个类灵活度比较高)
  • Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,会死锁
  • Synchronized 线程 1(获得锁后阻塞)、线程2(就会一直等下去);Lock锁就不一定会等待下
    去,因为可以用tryLock()去尝试去获取锁;
  • Synchronized 是可重入的,不可以中断,是非公平锁;Lock也是可重入锁(ReentrantLock是他的实现类),但是他可以中断锁,默认非公平,可以自行设置
  • Synchronized适合锁少量代码同步问题,Lock则适合大量的同步代码

3.4公平锁和非公平锁

在ReentrantLock中有两个属性公平锁和非公平锁,这样可以通过方法进行设置是否是公平锁,默认非公平的。

非公平锁:可以插队

公平锁:分先来后到

为什么默认是非公平的,如果用公平的,一个线程比如时间过长,会造成后面等待很久。而非公平的会先让时间短的先插队执行。

tips:阻塞和死锁区别

​ 阻塞:资源不足,出现排队现象

​ 死锁:多个线程抢同一个资源,都不释放,然后都拿不到

4.生产者和消费者问题

下面这些都是通过一个标志位控制一条线程生产,一条线程消费,核心的资源方法都写在Data里面,而线程的话在外面调用方法就可以了。

资源里面核心方法很简单就是三步:判断是否等待,等待的话就会将当前线程挂起,释放锁==》执行业务方法==》唤醒其他的线程,看看其他线程现在可以执行(其他线程会根据标志位来判断自己是否执行)。

4.1synchronized版的生产与消费

注意:下面不用if是为了防止虚假唤醒:没有通知,中断或者超时时候一个线程被唤醒。等待应该总是出现在循环中。

package com.pro_com;

import lombok.Synchronized;

import java.util.Date;

/**
 * Created by yj on 2020/8/18 22:53
 */
public class A {
    public static void main(String[] args) {

        Data data = new Data();
        //这样的话开启两个线程,一个线程执行add,一个del
        //第一个线程add后+1,这时候第一个线程他就只能等待了,因为数字为1
        //然后第二个线程del后-1,这时候第二个线程等待,数字为0了,这时候第一个线程又可以执行了,不等待了
        //这样就实现了进程之间通信
        new Thread(()->{
            for(int i=0;i<50;i++){
                try {
                    data.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=0;i<50;i++){
                try {
                    data.del();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        
		new Thread(()->{
            for(int i=0;i<50;i++){
                try {
                    data.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

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


class Data{//这儿都是资源

    //设置了一个flag用以判断
    private int number = 0;

    public synchronized void add() throws InterruptedException {
        //1.判断是否该等待,这儿不用if是为了防止虚假唤醒:没有通知,中断或者超时时候一个线程被唤醒。所以等待应该总是出现在循环中
        //不等于0要一直等待,不能只等待一次
        while(number!=0){
            this.wait();
        }
        //2业务方法
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //3.唤醒
        this.notifyAll();

    }

    public synchronized void del() throws InterruptedException {
        //1.判断是否该等待
        while(number==0){
            this.wait();
        }
        //2业务方法
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //3.唤醒
        this.notifyAll();
    }

}

image-20200819153550792

4.2JUC版的生产与消费

在java.util.concurrent下面找到这个Condition接口,他具有一些方法可以替代wait和notify操作,这里面lock取代了synchronized方法和语句,Condition则取代了对象监视器的使用方法。

也就是他和synchronized其实很多方法是一样的。下面方法是等价的。

image-20200819151141006

image-20200819150756611

  • 写法一

    写法一其实就是将上面的wait等方法直接换成了JUC的方法。

    package com.pro_com;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by yj on 2020/8/19 15:14
     */
    public class B  {
        public static void main(String[] args) {
            Data2 data = new Data2();
    
            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 Data2{ // 数字 资源类
        private int number = 0;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //condition.await(); // 等待
        //condition.signalAll(); // 唤醒全部
        //+1
        public void increment() throws InterruptedException {
            lock.lock();
            try {
                // 业务代码
                while (number!=0){  //0
                    // 等待
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName()+"=>"+number);
                // 通知其他线程,我+1完毕了
                condition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
    
        }
    
        //-1
        public  void decrement() throws InterruptedException {
            lock.lock();
            try {
                while (number==0){ // 1
                    // 等待
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName()+"=>"+number);
                // 通知其他线程,我-1完毕了
                condition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
    }
    

    image-20200819154000477

  • 写法二

    JUC的Condition监视器可以精准的去唤醒线程,可以让线程去按照顺序去执行,具体写法如下所示。

    上面两种写法则不能让其按照顺序来执行,每次执行完当前线程,下一次都是随机的。这儿的话就体现出了JUC版本的优势,它可以在当前线程执行完,可以精准的去唤醒下一个线程。

    package com.pro_com;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by yj on 2020/8/19 15:40
     */
    /**
     * 下面完成了这样一件事,就是需要A执行完调用B,B执行完调用C,C执行完调用A
    * */
    public class C {
        public static void main(String[] args) {
            Data3 data = new Data3();
    
            new Thread(()->{
                for (int i = 0; i <10 ; i++) {
                    data.printA();
                }
            },"A").start();
    
            new Thread(()->{
                for (int i = 0; i <10 ; i++) {
                    data.printB();
                }
            },"B").start();
    
            new Thread(()->{
                for (int i = 0; i <10 ; i++) {
                    data.printC();
                }
            },"C").start();
        }
    
    }
    
    class Data3{ // 资源类 Lock
    
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
        private int number = 1; // 1A  2B  3C
    
        public void printA(){
            lock.lock();
            try {
                // 业务,判断-> 执行-> 通知
                while (number!=1){
                    // 等待
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
                // 唤醒,唤醒指定的人,B
                number = 2;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printB(){
            lock.lock();
            try {
                // 业务,判断-> 执行-> 通知
                while (number!=2){
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
                // 唤醒,唤醒指定的人,c
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        public void printC(){
            lock.lock();
            try {
                // 业务,判断-> 执行-> 通知
                // 业务,判断-> 执行-> 通知
                while (number!=3){
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
                // 唤醒,唤醒指定的人,c
                number = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
    }
    

image-20200819155506333

5.到底什么是锁

5.1锁住的到底是什么

一定要注意锁的不是方法,对普通方法加锁,之后调用的时候,锁的是调用的对象。

如果对静态方法加锁,那么就是锁的是静态方法所在的类。

5.2只有一个对象的时候测试

package com.lock;

/**
 * Created by yj on 2020/8/19 16:14
 */

import java.util.concurrent.TimeUnit;

/**
 * 实际上下面不管是不是在方法里面加上延迟都是按顺序执行
 * 发短信和打电话,主要原因如果加了synchronized在方法上,他锁住的对象是这个方法调用者
 * 比如这儿就是锁的phone这个对象,在第一个线程执行phone.sendSms(),这个方法的调用对象phone就被锁住了
 * 这样的话其他线程都只能等待,而他用完后会自动释放锁,然后第二个线程执行phone.call()方法,拿到这个锁
 * 一定要注意锁的不是方法,对方法加锁,之后调用的时候,锁的是调用的对象,这儿对象都是同一个。
* */
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("打电话");
    }

}

5.3两个对象的时候测试

package com.lock;

/**
 * Created by yj on 2020/8/19 16:14
 */

import java.util.concurrent.TimeUnit;

/**
 * 1.下面不加锁的hello,是不受锁的影响的,该怎么执行怎么执行
 * 2.下面有两个对象的时候,线程执行相互不影响,因为sendSms有延迟,所以这儿先执行打电话,再执行发短信
 * 如果这儿只有一个对象,两个线程还是这么调用的话,因为锁的关系,就像前面一样,即使发短信有延迟,另一个线程也得等待
 */
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.4对两个静态加锁方法进行测试

如果在静态方法上测试的话,因为静态方法是类加载的时候加载的,所以锁的就是这个类。和上面是不一样的,上面锁的是实例对象。

package com.lock;

import java.util.concurrent.TimeUnit;

/**
 * Created by yj on 2020/8/19 16:14
 */
/**
*下面是先打印发短信,再打印打电话
*/
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("打电话");
    }


}

5.5对一个个静态加锁方法和一个普通方法进行测试

package com.lock;

/**
 * Created by yj on 2020/8/19 16:15
 */

import java.util.concurrent.TimeUnit;

/**
 * 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();
    }
}

// Phone3唯一的一个 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("打电话");
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值