JUC学习

Posted 三笠·阿卡曼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC学习相关的知识,希望对你有一定的参考价值。

线程基础知识复习

多线程的编程步骤

  • 创建资源类,在资源类中创建属性和操作方法;
  • 在操作方法中进行判断、干活、通知
  • 创建多个线程,调用资源类的操作方法;
  • 要防止虚假唤醒问题,要把判断条件改成while

为什么多线程极其重要

硬件方面

摩尔定律失效
它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为:
当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。
换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。
摩尔定律失效。
在主频不再提高且核数在不断增加的情况下,要想让程序更快就要用到并行或并发编程。

软件方面

高并发系统:需要处理异步+回调等生产需求;

多线程相关基本概念

进程:是程序的一次执行,,是系统进行资源分配和调度的基本单位,每一个进程都有自己的内存空间和系统资源;
线程:在同一个进程中又可以执行多个任务,而这每一个任务就可以看作是一个线程,一个进程会有1~多个线程;

用户线程和守护线程

Java线程分为用户线程和守护线程;
线程的daemon属性为true表示是守护线程,false表示是用户线程;

  • 用户线程:是系统的工作线程,他会完成这个程序需要完成的业务操作;
  • 守护线程:是一种特殊的线程,在后台默默的完成一些系统性的服务,比如垃圾回收线程;

总结

  • 当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出
    • 如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出;
  • 设置守护线程,需要在start()方法之前进行

实现线程的四种方式

  • 继承Thread类;
  • 实现Runnable接口;
  • 实现Callable接口;
  • 线程池实现;

CompletableFuture

Future和Callable接口

  • Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
    在这里插入图片描述

  • Callable接口中定义了需要有返回的任务需要实现的方法。

  • 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,过了一会才去获取子任务的执行结果。

JUC学习

JUC概述

什么是JUC

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

进程和线程的概念

  • 进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 总结来说:
    • 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
    • 线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

线程的状态

  • NEW,(新建)
  • RUNNABLE,(准备就绪)
  • BLOCKED,(阻塞)
  • WAITING,(不见不散)
  • TIMED_WAITING,(过时不候)
  • TERMINATED;(终结)

wait和sleep的区别

  • sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用;
  • sleep不会释放锁,他也不需要占用锁;wait会释放锁,调用它的前提是当前线程占有锁(代码要在synchronized中);
  • 他们都可以被interrupted方法中断;

管程(Monitor,监视器)

是一种同步机制,保证同一个时间,只要一个线程访问被保护的数据或者代码;jvm同步基于进入和退出,使用管程对象实现的;

用户线程与守护线程

  • 用户线程:
  • 守护线程:

LOCK接口讲解

Synchronized关键字回顾

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    • 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的
    所有对象;
  4. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主
    的对象是这个类的所有对象。

什么是Lock锁

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

Lock和Synchronized的区别

  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
    • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Lock接口

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Con

lock方法

lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在finally 块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用 Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}

newCondition

关键字 synchronized 与 ==wait()/notify()==这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类也可以实现等待/通知模式。用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知, Condition 比较常用的两个方法:

  • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。
  • signal()用于唤醒一个等待的线程。

注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。

ReentrantLock

ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体看一下如何使用。

小结

Lock 和 synchronized 有以下几点不同:

  • Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
  • synchronized 在发生异常时,会==自动释放线程占有的锁,=因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  • Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  • Lock 可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。

线程间通信

synchronized实现线程间交替打印(线程间通信)

始终牢记方法:

* 创建资源类,在资源类中创建属性和操作方法;
* 在操作方法中进行判断、干活、通知
* 创建多个线程,调用资源类的操作方法;
package com.vleus.juc.sync;

/**
 * @author vleus
 * @date 2021年07月14日 22:06
 */
//第一步:创建资源类,定义属性和方法
class Share {

    //初始值
    private int number = 0;

    //+1方法
    public synchronized void incr() throws InterruptedException {

        //第二步:判断 干活 通知
        if (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 {
        // 判断
        if (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 = 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();

    }
}

输出效果如下:
在这里插入图片描述

wait可能导致虚假唤醒

多个线程执行上述交替打印会出现虚假唤醒的问题;需要将判断条件放到while循环中;
在这里插入图片描述

Lock锁实现1、0的交替打印

package com.vleus.juc.lock;

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

/**
 * @author vleus
 * @date 2021年07月14日 22:35
 */

//创建资源类,定义属性和方法
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();
    }

}

打印结果如下:
在这里插入图片描述

线程间的定制化通信

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

  • AA打印五次,BB打印10次,CC打印15次;
  • AA打印五次,BB打印10次,CC打印15次;
    进行10轮;
    在这里插入图片描述
package com.vleus.juc.lock;

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

/**
 * @author vleus
 * @date 2021年07月14日 23:06
 */
//第一步:创建资源类,定义属性和操作方法
class ShareResource{

    //定义标志位
    private int flag = 1; //1 AA 2 BB 3 CC

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

    //创建3个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; //先修改标志位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; //先修改标志位2
            c3.signal(); //通知BB线程
        }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; //先修改标志位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 juc学习四(集合不安全问题)

JUC 系列:AQS -->学习 Doug Lea 大神思想

JUC 系列:AQS -->学习 Doug Lea 大神思想

JUC 系列:AQS -->学习 Doug Lea 大神思想

JUC 系列:AQS -->学习 Doug Lea 大神思想

Java多线程之JUC包:Semaphore源码学习笔记