栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

《JUC并发编程》JUC概述 | Lock接口 | 线程间通信 | 多线程锁

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《JUC并发编程》JUC概述 | Lock接口 | 线程间通信 | 多线程锁

博客首页:热爱编程的大李子 

文章目的:JUC概述 | Lock接口 | 线程间通信 | 多线程锁总结

博主在学习阶段,如若发现问题,请告知,非常感谢

同时也非常感谢各位小伙伴们的支持

每日一语:我生来就是高山而非溪流 ,我欲于群峰之巅俯视平庸的沟壑。我生来就是人杰而非草芥,我站在伟人之肩藐视卑微的懦夫!

感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们!

最后,祝大家每天进步亿点点! 欢迎大家点赞➕收藏⭐️➕评论支持博主爛!


文章目录
    • 1、什么是 JUC
      • 1.1 JUC简介
      • 1.2 进程与线程
      • 1.3 线程的状态
        • 1.3.1 线程状态枚举类
        • 1.3.2 wait/sleep的区别
      • 1.4 并发与并行
        • 1.4.1 串行模式
        • 1.4.2 并行模式
        • 1.4.3 并发
    • 2、Lock 接口
      • 2.1 Synchronized
        • 2.1.1 Synchronized 关键字回顾
        • 2.1.2 售票案例
      • 2.2 什么是Lock
        • 2.2.1 Lock接口
        • 2.2.2 lock接口的常见方法
        • 2.2.3 如何使用
      • 2.3 使用Lock和Lambda Express改进卖票案例
      • 2.4 、总结创建线程的几种方式
    • 3、Java8之lambda表达式复习
    • 4、线程间通信
      • 4.1 synchronized实现
      • 4.2 Lock 方案
      • 4.3 线程间定制化调用通信
    • 5、多线程锁
    • 总结


1、什么是 JUC 1.1 JUC简介

在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

1.2 进程与线程

**进程:**进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

进程/线程例子?

  • 使用QQ,查看进程一定有一个QQ.exe的进程,我可以用QQ和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。

  • 大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。

  • word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查

1.3 线程的状态 1.3.1 线程状态枚举类

Thread.State

public enum State {
    
    NEW,(新建)

    
    RUNNABLE,(准备就绪)

    
    BLOCKED,(阻塞)

    
    WAITING,(不见不散)

    
    TIMED_WAITING,(过时不候)

    
    TERMINATED;(终结)
}
1.3.2 wait/sleep的区别

功能都是当前线程暂停

  • wait放开手去睡,放开手里的锁
  • sleep握紧手去睡,醒了手里还有锁
1.4 并发与并行 1.4.1 串行模式

串行表示所有任务都一 一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。
串行是一次只能取得一个任务,并执行这个任务。

1.4.2 并行模式

并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上则依赖于多核CPU。

一句话:多项工作一起执行,之后再汇总

**例子:**泡方便面,一边电水壶烧水,一边撕调料倒入桶中

1.4.3 并发

并发(concurrent) 指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。一句话:同一时刻多个线程在访问同一个资源,多个线程对一个点

**例子:**小米9 今天上午10点,限量抢购;春运抢票; 电商秒杀…

2、Lock 接口 2.1 Synchronized 2.1.1 Synchronized 关键字回顾

synchronized 是Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized 后面括号括起来的部分,作用的对象是这个类的所有对象。
2.1.2 售票案例
//口诀:在高内聚低耦合环境下,线程 操作 资源类
//第一步  创建资源类,定义属性和和操作方法
class Ticket {
    //票数
    private int number = 30;
    //操作方法:卖票
    public synchronized void sale() {
        //判断:是否有票
        if(number > 0) {
            System.out.println(Thread.currentThread().getName()+" : 卖出第:"+(number--)+"张票, 剩下:"+number+"张");
        }
    }
}

public class SaleTicket {
    //第二步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        //创建Ticket对象
        Ticket ticket = new Ticket();
        //创建三个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //调用卖票方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //调用卖票方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //调用卖票方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"CC").start();
    }
}

如果一个代码块被synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
**1)**获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
**2)**线程执行发生异常,此时JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO 或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock 就可以办到。

2.2 什么是Lock 2.2.1 Lock接口

Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比synchronized 更多的功能。


Lock 与Synchronized 的区别

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
2.2.2 lock接口的常见方法
  • lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待

  • 关键字synchronized 与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的newContition()方法返回Condition 对象,Condition 类也可以实现等待/通知模式。

    Condition 比较常用的两个方法:

    • await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
    • signal()用于唤醒一个等待的线程。
2.2.3 如何使用
class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...
 
   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }
2.3 使用Lock和Lambda Express改进卖票案例
package com.rg.lock;

import java.util.concurrent.locks.ReentrantLock;

//第一步  创建资源类,定义属性和和操作方法
class LTicket {
    //票数量
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock(true);
    //卖票方法
    public void sale() {
        //上锁
        lock.lock();
        try {
            //判断是否有票
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+" : 卖出第:"+(number--)+"张票, 剩下:"+number+"张");
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class LSaleTicket {
    //第二步 创建多个线程,调用资源类的操作方法
    //创建三个线程
    public static void main(String[] args) {

        LTicket ticket = new LTicket();

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

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

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}
2.4 、总结创建线程的几种方式
  • 继承Thread

    public class SaleTicket extends Thread 改进: java是单继承,资源宝贵,要用接口方式

  • 使用 Thread(Runnable target, String name)

    • 新建类实现runnable接口 — 这种方法会新增类,有更新更好的方法
    class MyThread implements Runnable//新建类实现runnable接口
     new Thread(new MyThread,...)
    
    • 匿名内部类 — 这种方法不需要创建新的类,可以new接口
    new Thread(new Runnable() {
        @Override
        public void run() {
    
        }
       }, "your thread name").start();
    
    • lambda表达式 — 这种方法代码更简洁精炼
    new Thread(() -> {}, "your thread name").start();
    

错误的写法:

Thread t1 = new Thread();
t1.start();

补充:调用start时候,线程是否马上进行创建?

不一定,start()底层使用的是native方法,是操作系统的方法.
具体什么时候创建由操作系统决定.

3、Java8之lambda表达式复习

Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称
为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分:

  • 左侧:指定了 Lambda 表达式需要的所有参数
  • 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

代码演示

package com.atguigu.thread;
@FunctionalInterface
interface Foo{
 
    // public void sayHello() ;   

    public int add(int x,int y);

    //函数式接口可以有多个 default实现
    //JDK1.8之后 接口里面可以有方法的实现
    default int div(int x,int y) {
        return x/y;
    }

    //可以有多个静态函数
    public static int sub(int x,int y) {
        return x-y;
    }
}


public class LambdaDemo
{
    public static void main(String[] args)
    {
        //使用匿名内部类方式调用接口中的方法方式
        // Foo foo = new Foo() {
        //     @Override
        //     public void sayHello() {
        //         System.out.println("Hello!!");
        //     }
        // foo.sayHello();

        //使用Lambda方式
        Foo foo = (x,y)->{
            System.out.println("Hello!! lambda !!");
            return x+y;
        };

        int result = foo.add(3,5);
        System.out.println("******result="+result);
        System.out.println("******result div="+foo.div(10, 2));
        System.out.println("******result sub="+Foo.sub(10, 2));

    }
}
4、线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。

我们来基本一道面试常见的题目来分析

场景: 两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求 用线程间通信

4.1 synchronized实现
package com.rg.sync;

//第一步 创建资源类,定义属性和操作方法
class Share {
    //初始值
    private int number = 0;
    //+1的方法
    public synchronized void incr() throws InterruptedException {
        //第二步 判断 干活 通知
        while(number != 0) { //判断number值是否是0,如果不是0,等待
            this.wait(); //在哪里睡,就在哪里醒
        }
        //如果number值是0,就+1操作
        number++;
        System.out.println(Thread.currentThread().getName()+" :: "+number);
        //通知其他线程
        this.notifyAll();
    }

    //-1的方法
    public synchronized void decr() throws InterruptedException {
        //判断
        while(number != 1) {
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName()+" :: "+number);
        //通知其他线程
        this.notifyAll();
    }
}
 

public class ThreadDemo1 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        //创建线程
        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.incr(); //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.incr(); //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 1; i <=10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

**结果分析:**当只有AA,BB线程时,一切运行正常, AA::1和BB::0 轮流执行

当增加CC、DD线程时,会出现虚假唤醒的情况,

原因分析:


4.2 Lock 方案
package com.rg.lock;

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


//第一步 创建资源类,定义属性和操作方法
class Share{
    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //+1
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();

        try {
            //判断
            while (number != 0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            //通知
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }
    }

    //-1
    public void decr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
           //判断
            while (number!=1){
                condition.await();
            }
           //干活
            number--;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            //通知
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}


 

public class ThreadDemo2 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        //创建线程
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();//加1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();//加1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();//加1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();//加1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}
4.3 线程间定制化调用通信

三个线程启动,要求如下:

AA打印5次,BB打印10次,CC打印15次.

接着

AA打印5次,BB打印10次,CC打印15次

…来10轮

实现代码

package com.rg.lock;

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



//第一步 创建资源类
class ShareResource{
    //定义标志位
    private int flag = 1;//1  AA  2 BB  3 CC

    //创建Lock锁
    private Lock lock = new ReentrantLock();

    //创建三个Condition,也就是三把钥匙
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag!=1){
                //等待
                c1.await();
            }
            //干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
            }
            //通知
            flag = 2;
            c2.signal();//通知BB线程(精准通知)
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag!=2){
                //等待
                c2.await();
            }
            //干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
            }
            //通知
            flag = 3;
            c3.signal();//通知CC线程(精准通知)
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    //打印15次,参数第几轮
    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag!=3){
                //等待
                c3.await();
            }
            //干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
            }
            //通知
            flag = 1;
            c1.signal();//通知AA线程(精准通知)
        }finally {
            //释放锁
            lock.unlock();
        }
    }

}


public class ThreadDemo3 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <= 10 ; i++) {
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}
5、多线程锁

经典的八锁问题

  1. 标准访问,先打印短信还是邮件
  2. 停4秒在短信方法内,先打印短信还是邮件
  3. 普通的hello方法,是先打短信还是hello
  4. 现在有两部手机,先打印短信还是邮件
  5. 两个静态同步方法,1部手机,先打印短信还是邮件
  6. 两个静态同步方法,2部手机,先打印短信还是邮件
  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

参考代码

package com.rg.sync;

import java.util.concurrent.TimeUnit;


class Phone {
    public static synchronized void sendEmail() throws Exception{
        try {
            TimeUnit.SECONDS.sleep(4);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public  synchronized void sendSMS()throws Exception{
        System.out.println("------sendSMS");
    }

    public void sayHello(){
        System.out.println("------sayHello");
    }
}



public class Lock_8{
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                // phone.sendSMS();
                // phone.sayHello();
                phone2.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();
    }
}
总结

OK,今天关于 JUC的知识分享 就到这里,希望本篇文章能够帮助到大家,同时也希望大家看后能学有所获!!!

好了,我们下期见~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/840670.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号