狂神说JAVA-JUC并发编程(一)线程、生产者消费者、锁、synchronized、Lock

课程地址:【狂神说Java】JUC并发编程最新版通俗易懂

准备工作

新建一个Maven项目,引入一个lombok依赖.

 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>

在这里插入图片描述
java Compiler改成8
在这里插入图片描述

什么是JUC

Java.util.concurrent
在这里插入图片描述
进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 mian、GC

线程:开了一个进程 Typora,写字,自动保存(线程负责的)

Java 真的可以开启线程吗?

java是开不了线程的

new Thread().start();

我们点进start()

public synchronized void start() {
    
    
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
    
    
            start0();
            started = true;
        } finally {
    
    
            try {
    
    
                if (!started) {
    
    
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
    
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

首先。start()是一个synchronized方法,同步方法,安全,这个方法会把当前线程加入一个线程组,调用了start0()方法,这个start0(),是用native修饰的,也就是本地方法。所以最后是调用了本地方法,JAV是没有权限开启线程的。start()调用了本地的C++方法,因为java是运行在虚拟机之上的,无法直接操作硬件

并发、并行

并发:cpu只有一个核:多线程操作同一个资源(cpu通过线程间的快速交替,模拟出来多条线程,看似并行,实际串行)
并行:多个人一起行走,cpu有多个核,多个线程可以同时执行,可以通过线程池完成
并发编程的本质:充分利用CPU的资源

查看处理器的方法

①任务管理器
如图:最大可以同时处理12条线程
在这里插入图片描述

②设备管理器也可以看有多少个处理器
在这里插入图片描述

③通过代码

public static void main(String[] args) {
    
    
        //new Thread().start();
       //获取CPU核数 CPU密集型,IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

在这里插入图片描述

线程的状态

Thread.State;

咱们点进state就可以看到了

public enum State {
    
    
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}

wait()和sleep()的区别
1:来自不同的类
wait()来自Object类,sleep()来自Thread类

2 关于锁的释放
wait()会释放锁,sleep()不会释放锁,可以理解为抱着锁睡觉

3使用范围不同
sleep()可以在任何地方使用,wait()只能在同步代码块中使用

4是否需要捕获异常
wait()不需要捕获异常
sleep()必须要捕获异常

Lock锁
传统的synchronized锁

在公司真正的多线程开发中,线程就是一个单独的资源类,没有任何附属的操作(类中只有属性和方法),为了降低耦合性,不会用类去实现接口,因为实现类接口就不是OOP编程了,而且实现了接口的话耦合性变高,如果让类实现了Runnable,这个类就只是一个线程类了

如下代码,只是把Ticket作为了资源类,并没有让它实现Runnable接口

package com.kuang.demo01;

public class SaleTicketDemo01 {
    
    
    public static void main(String[] args) {
    
    
        Ticket ticket = new Ticket();
       // ticket.sale();
//并发,多线程操作同一个资源类,把资源类丢进线程
//        new Thread(new Runnable() { //这种方式教匿名内部类,但是太繁琐了,于是我们向导用lambda表达式
//            public void run() {
    
    
//
//            }
//        }).start();
      //函数式接口,jdk1.8 lambda表达式(参数)->{代码}

        new Thread(()->{
    
    //3个线程操作同一个资源,这种方式对于Ticket类来说实现类解耦,因为Ticket类是一个纯类,没有实现Runnable接口
            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{
    
    
public int number=50;
//synchronized本质是队列,锁
public synchronized void sale(){
    
    
if(number>0)
{
    
    
    System.out.println(Thread.currentThread().getName()+"获得了第"+number--+"票");
}

}

}

在这里插入图片描述
Lock接口
所有已知实现类:
ReentrantLock(可重入锁) , ReentrantReadWriteLock.ReadLock (读锁), ReentrantReadWriteLock.WriteLock (写锁)

公平锁:十分公平,先来后到,排队.
非公平锁:不公平,可以插队
默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗

ReentrantLock() 无参时默认是非公平锁

public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }

用之前加锁,用完解锁

Lock三部曲
new 锁
加锁
解锁(在finally中,lock.unlock())

package com.kuang.demo01;

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

public class SalwTicketDemo02 {
    
    
    public static void main(String[] args) {
    
    
       Ticket2 ticket = new Ticket2();
        new Thread(()->{
    
    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{
    
    
    Lock lock=new ReentrantLock();
        public int number=50;
        //synchronized本质是队列,锁
        public synchronized void sale() {
    
    
            lock.lock();
            try {
    
    
                //业务代码
                if (number > 0) {
    
    
                     System.out.println(Thread.currentThread().getName() + "获得了第" + number-- + "票");
                 }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.unlock();//解锁
            }
        }
    }

在这里插入图片描述

Synchronized和Lock锁的区别
1 Synchronized是内置的Java关键字,Lock是一个java类(还是接口呢?)
2 Synchronized无法判断获取锁的状态,Lock可以判断是否获取了锁
3 Synchronized会自动释放锁,Lock锁必须要手动释放锁,如果不释放锁,会死锁
4 如果有两个线程,使用Synchronized时,如果线程1获得了锁,那么线程2会等待,线程1阻塞了,线程2也会死等。使用Lock锁时,Lock锁不一定会等待,Lock有一个方法,Lock.tryLock();会去尝试获取锁
5 Synchronized 可重入锁,不看可以在中断,非公平。 Lock:可重入,可以判断锁,可以设置公平或者非公平
6 Synchronized适合锁少量的代码同步问题。Lock锁适合锁大量的同步代码

生产者消费者问题

面试高频: 单例模式, 八大排序,生产者消费者,死锁

①synchronized版本,用wait()和notify()
生产者消费者骨架:等待、执行业务、通知

package com.kuang.demo01;
/*
线程之间的通信问题:生产者和消费者问题
线程交替执行 A B操作同一个变量 num=0 A让num+1,B让num-1;
 **/

public class ProductAndConsumer {
    
    
    public static void main(String[] args) {
    
    
        Date date = new Date();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    date.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    date.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

    }

}

//生产者消费者骨架:等待、执行业务、通知
class Date{
    
    //数字,资源类
    private int num=0;

    public synchronized void increment() throws InterruptedException {
    
    
        if(num!=0)
        {
    
    
           //等待操作
             this.wait();
        }
        num++;
        //执行完++,通知其他线程我已经完成++操作
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
    
    
        if(num==0)
        {
    
    
//等待操作
            this.wait();
        }
        num--;
        //执行完--,通知其他线程我已经完成--操作
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        this.notifyAll();
    }
}

在这里插入图片描述
synchronized版本存在的问题

如果增加两个线程,即两个线程加,两个线程减,得到如下结果,并不能1,0交替,而且出现了2,3

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

在这里插入图片描述

问题原因分析
咱们看jdk1.8的官方文档,找到Object类的wait()方法

导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }  ```

结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

跟cpu调度有关,运气好没问题也是可能的.主要原因还是因为wait会释放锁,会导致两个num++/num–的线程同时进入wait队列。当线程被唤醒后,A先获得锁++,这时侯如果cpu调度到B,B就会继续往下执行++.如果调度是有序的(++,–,++,–),也不会有这种问题

解决方法
用if会出现,判断过了,但是拿到的是之前的值
把if判断改成while判断等待,因为if判断进if之后不会停,用while判断的话,变量一旦被修改,另外一个线程拿到锁之后,就会等待,防止虚假唤醒

在这里插入图片描述

② 用Lock锁完成生产者消费者问题

在这里插入图片描述

package com.kuang.demo01;

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

public class prodectAndConsumerLock {
    
    
    public static void main(String[] args) {
    
    
        Date2 date = new Date2();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    date.increment();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    date.decrement();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"B").start();

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

    }
}
class Date2{
    
    
    private int num=0;
    Lock lock=new ReentrantLock();
    Condition condition = lock.newCondition();

    public  void increment() throws InterruptedException {
    
    


        try {
    
    
            lock.lock();//锁
            //业务代码
            while(num!=0)
            {
    
    
                //等待操作
                condition.await();
            }
            num++;
            //执行完++,通知其他线程我已经完成++操作
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            condition.signalAll();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //释放锁
            lock.unlock();
        }

    }

    public  void decrement() throws InterruptedException {
    
    
        try {
    
    
            lock.lock();

            while(num==0)
            {
    
    
//等待操作
                condition.await();
            }
            num--;
            //执行完--,通知其他线程我已经完成--操作
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            condition.signalAll();


        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

在这里插入图片描述

用Lock锁完成生产者消费者问题改进,Condition 精准的通知和唤醒线程、
让ABCD四个线程交替执行

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!

Condition 精准的通知和唤醒线程

package com.kuang.demo01;

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

//A执行完通知 B,B执行完通知C
public class ProductAndConsumerLockJiaoti {
    
    
    public static void main(String[] args) {
    
    
        Data3 data3 = new Data3();


        new Thread(()->{
    
    
            for (int i = 0; i < 5; i++) {
    
    
                try {
    
    
                    data3.printA();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
    
    

            for (int i = 0; i < 5; i++) {
    
    
                try {
    
    
                    data3.printB();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

        },"B").start();
        new Thread(()->{
    
    

            for (int i = 0; i < 5; i++) {
    
    
                try {
    
    
                    data3.printC();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data3{
    
    
    Lock lock=new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3= lock.newCondition();
    private int state=1;
    int num=1;

public void printA() throws InterruptedException {
    
    
    lock.lock();
    try {
    
    
        while(state!=1)
        {
    
    
            condition1.await();
        }
        System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>AAAAA");
        condition2.signal();//A执行完通知B
        state=2;//改变state的状态
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        lock.unlock();
    }
}
    public void printB() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while(state!=2)
            {
    
    
                condition2.await();
            }
            System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>BBBBB");
            condition3.signal();//A执行完通知B
            state=3;//改变state的状态
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
    public void printC() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while(state!=3)
            {
    
    
                condition3.await();
            }
            System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>CCCCC");
            condition1.signal();//A执行完通知B
            state=1;//改变state的状态
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

在这里插入图片描述

8锁现象(锁是什么,如何判断锁的是谁)

锁只会锁两个东西,一个是new出来的对象,一个是class模板

8锁,就是关于锁的8个问题

问题1:

package com.kuang.lock8;
/*
 8锁,就是关于所得8个问题
 问题1:标准情况下,先打印发短信,还是先打印打电话?结果是先打印发短信,后打印打电话
 原因:不能回答先调用A线程,这是错误的,不是先调用先执行,这是锁的问题,因为被Synchronized修饰的
 方法,锁的对象是方法的调用者,所以盗用两个方法的对象都是phone,但是现在phone只有一个,也就是说着两个方
 法现在用的是同一把锁,谁先拿到,谁就先执行
*/
import java.util.concurrent.TimeUnit;

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 (Exception e) {
    
    
            e.printStackTrace();
        }
        new Thread(()->{
    
    
            phone.call();

        },"B").start();
    }
}
class phone{
    
    


    public synchronized void sendSms()
    {
    
    
        System.out.println("sendSms");
    }
    public synchronized  void call()
    {
    
    
        System.out.println("call");
    }
}

在这里插入图片描述

问题2:给发短信方法加延时4s,程序的执行情况

 public synchronized void sendSms()
    {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(4);

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }

结果:经过4s之后打印发短信,然后立马打印打电话,如果把主函数中延时改为5s,那么陈恒徐运行情况是经过4s打印发送短信,然后经过1s打印打电话(主程序的延时是同时进行的?)
在这里插入图片描述
问题3:当调用普通方法,而不是synchronized方法时,先输出什么
答:先执行普通方法,因为普通方法没有锁,不受锁的影响

public class Test3{
    
    
    public static void main(String[] args) {
    
    
        phone2 phone = new phone2();
        new Thread(()->{
    
    
            phone.sendSms();

        },"A").start();
        try {
    
    
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        new Thread(()->{
    
    
            phone.sayHello();

        },"B").start();
    }
}
class phone2{
    
    
    public synchronized void sendSms()
    {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(4);

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }
    public  void sayHello()
    {
    
    
        System.out.println("hello");
    }
}

问题4:两个对象分别调用synchronized方法时,先输出什么
因为锁不一样,所以耗时短的先输出

问题5:一个对象,把两个synchronized方法改成静态synchronized方法,即static synchronized方法,先输出哪个?
答:发短信:static方法在类加载时就有了,锁的对象是class模板,因为class对象是唯一的 phone2.class全局唯一,因为两个方法都被static修饰了,所以两个方法用的是同一个锁,且锁的是类模板

问题6:2个对象,把两个synchronized方法改成静态synchronized方法,即static synchronized方法,先输出哪个?
答:发短信,phone2.class全局唯一(两个对象的class类模板只有一个),因为两个方法都被static修饰了,所以两个方法用的是同一个锁,且锁的是类模板

问题7:同一个对象,把两个synchronized方法中的一个改成静态synchronized方法,即static synchronized方法,另一个为普通synchronized方法,先输出哪个?

答:打电话,锁的对象不一样,一个锁的是类模板,一个锁的是对象,后面调用的方法不需要去等待锁

问题8:两个对象,把两个synchronized方法中的一个改成静态synchronized方法,即static synchronized方法,另一个为普通synchronized方法,先输出哪个?
答:打电话,还是锁的对象不一样

小结
当同步方法不用static修饰的时候:锁的是对象,是一个具体的手机
当同步方法用static修饰的时候:锁的是类模板,是唯一的

附件(包括代码和笔记、jdk文档)

链接:https://pan.baidu.com/s/1-0MCWtBGxxljpcqY0EwR5Q
提取码:1nc7

猜你喜欢

转载自blog.csdn.net/ningmengshuxiawo/article/details/113144941
今日推荐