Java多线程和JUC

本文详细介绍了Java多线程的概念、创建方法、线程状态及常用方法,强调了线程同步的重要性并讲解了synchronized和Lock锁的使用。此外,文章深入探讨了Java并发编程库JUC,包括线程池、锁机制、并发容器和线程通信等关键知识点,旨在帮助读者全面理解Java并发编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考
【狂神说Java】多线程详解
【狂神说Java】JUC并发编程最新版通俗易懂

一、多线程

1.1 线程简介

程序: 是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码(程序是静态的)
进程: 是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。
线程: 进程可进一步细化为线程, 是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。线程是CPU调度和执行的单位

注意:很多多线程是模拟出来的,真正的多线程是指多个CPU。如果是模拟出来的多线程,即在同一个CPU下,同一时间CPU只能执行一段代码,因为切换很快就有同时执行的错觉

并行和并发
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)

核心概念

  • 线程就是独立执行的路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、GC线程
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开劈了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为干预
  • 对同一份资源操作时,会存在抢资源的问题,需要加入并发控制
  • 线程会带来额外开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

1.2 线程的创建

线程不一定立即执行,CPU安排调度

第一种:继承Thread类
第一步:自定义线程类继承Thread类
第二步:重写run()方法,编写线程执行体
第三步:创建线程对象,调用start()方法启动线程

第二种:实现Runnable接口
第一步:自定义线程类实现Runnable接口
第二步:重写run()方法,编写线程执行体
第三步:创建线程对象,通过线程对象调用start()方法启动线程
推荐使用,避免了单继承的局限性,方便一个对象被多个线程使用)

第三种:实现Callable接口 (现阶段了解)
第一步:实现Callable接口,需要返回值
第二步:重写call方法,需要抛出异常
第三步:创建目标对象
第四步:创建执行服务:ExecutorServices ser = Executors.newFixedThreadPool(3);
第五步:提交执行:Future< Boolean > result1 = ser.submit(t1)
第六步:获取结果:boolean r1 = result.get()
第七步:关闭服务:ser.shutdownNow();

扩展

扩展1:静态代理

https://www.cnblogs.com/tele-share/p/9464295.html

静态代理应用:
对比以实现Runnable接口的形式创建多线程,可以发现,代理角色Thread类不需要我们创建,我们只需要写委托对象
实现Runnable接口.把委托对象的引用传递给Thread,借助Thread对象来开启线程即可

扩展2:lambda表达式
优点:
避免匿名内部类定义过多,简洁

Lambda演变方式:
1、前提:定义一个函数式接口(接口中只包含一个抽象方法),对于函数式接口可以通过Lambda表达式创建该接口的对象
2、实现类,创建对象,对象调用方法
3、静态内部类,创建对象,对象调用方法
4、局部内部类,创建对象,对象调用方法
5、匿名内部类,没有类的名称,必须借助接口或者父类
6、lambda表达式(用于函数式接口)

lambda表达式应用:
Runnable接口中只有public abstract void run();方法,符合函数式接口,可以使用Lambda表达式创建该接口的对象,之后传入Thread中(要传入Runnable实现类)

new Thread( () -> System.out.println("多学习多线程。。")).start();

1.3 线程状态

五大状态:
在这里插入图片描述

1.4 线程常用方法

start():启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
run():线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
currentThread :Thread类中一个静态方法:获取当前正在执行的线程
setName: 设置线程名字
getName: 读取线程名字

stop方法:
过期方法,不建议使用
建议线程正常停止
建议使用标志位,设置flag

//自己编写stop方法
public class Test07 implements Runnable{
    //设置停止的标志位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run方法"+i++);
        }
    }

    //使用标志位flag,自己设置stop()方法
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        Test07 t = new Test07();
        new Thread(t).start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main--"+i);
            if (i == 90){
                t.stop();
                System.out.println("该线程停止了");
            }
        }
    }
}

sleep方法:
sleep指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一个所,sleep不会释放锁

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

//测试sleep,倒计时,打印系统时间
public class Test08 {

    public static void main(String[] args) {
        //打印系统时间
        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        while (true){
            Date d = new Date();
            System.out.println(df.format(d));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        /*十秒倒计时
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
    }

    //十秒钟倒计时
    public static void tenDown() throws InterruptedException {
        int i = 10;
        while (true){
            System.out.println(i--);
            Thread.sleep(1000);
            if (i<=0){
                break;
            }
        }
    }
}

yield方法:
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功!看CPU心情

//测试礼让yield
public class Test09 {
    public static void main(String[] args) {
        MyYield my = new MyYield();
        
        new Thread(my,"a").start();
        new Thread(my,"b").start();
    }
}


class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        Thread.yield();//线程礼让
        System.out.println(Thread.currentThread().getName()+"执行结束");
    }
}

join方法:
join合并线程,待此线程执行完之后再执行其他线程,其他线程阻塞
可以想象成插队

//测试join
public class Test10 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("VIP来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test10 t = new Test10();
        Thread thread = new Thread(t);
        thread.start();

        for (int i = 0; i < 100; i++) {
            if (i==50){
                thread.join();
            }
            System.out.println("main--"+i);
        }
    }
}

Thread.State 查看线程状态

//观测线程的状态
public class Test11 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("----------");
        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();//更新状态
        System.out.println(state);

        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();//每次都要更新状态
            System.out.println(state);
        }
    }
}

设置线程优先级
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 1
Thread.NORM_PRIORITY = 1
getPriority(),serPriority(int XXX)

//测试优先级
public class Test12 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());//主程序的
        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority,"t1");
        Thread t2 = new Thread(myPriority,"t2");
        Thread t3 = new Thread(myPriority,"t3");
        Thread t4 = new Thread(myPriority,"t4");
        Thread t5 = new Thread(myPriority,"t5");


        //先设置优先级,再启动
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(2);
        t3.start();

        t4.setPriority(1);
        t4.start();

        t5.setPriority(10);
        t5.start();
    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());
    }
}

守护线程:
线程分为用户线程守护线程
虚拟机必须确保用户线程执行完毕(main…)
虚拟机不用等待守护线程执行完毕
如:后台记录操作日志,监控内存,垃圾回收等待…

Thread thread = new Thread(god);
thread.setDaemon(true);//true表示守护线程,默认false表示用户线程
thread.start();

1.4 线程同步(线程安全问题)

多线程操作同一资源
并发:同一个对象被多个线程同时操作
线程同步其实就是一种等待机制 / 排队

队列+锁 才能保证线程同步的安全性
synchronized

不安全的例子

//线程不安全的集合
import java.util.ArrayList;
import java.util.List;

//ArrayList不安全的集合
public class Test13 {
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        System.out.println(list.size());//结果不等于10000,因为有可能两个线程同时写一个位置,发生覆盖
    }
}

解决方法:
1、同步方法
2、同步代码块
3、Lock锁

synchronized 方法 默认锁的是这个对象(this),类本身;synchronized代码块可以锁任何对象锁的对象就是变化的量,需要增删改的对象

synchronized加在操作变化的量的方法或代码块上

import java.util.ArrayList;
import java.util.List;

//synchronized代码块,解决ArrayList不安全的集合
public class Test15 {
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);//不加这一段代码结果仍然不一样
            //去掉sleep不安全的原因应该是:输出的操作总稍快于最后几步(创建线程和list.add)
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持
3、不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺
4、循环等待:若干进程之间形成的一种头尾相接的循环等待资源关系

Lock锁
JDK5.0开始Java提供了通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
ReentrantLock类实现了Lock,它拥有和synchronized相同的并发性内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

lock()方法加锁,unlock()方法解锁
推荐放在try{} finally{} 代码块中

import java.util.concurrent.locks.ReentrantLock;

//Lock锁解决买票问题
public class Test16 {
    public static void main(String[] args) {
        TestBuy buy = new TestBuy();

        new Thread(buy).start();
        new Thread(buy).start();
        new Thread(buy).start();
    }

}

class TestBuy implements Runnable{
    private int ticketNum = 10;
    private final ReentrantLock lock = new ReentrantLock();//显示的定义锁
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();//加锁
                if (ticketNum>0){
                    System.out.println(ticketNum--);
                }else {
                    break;
                }
            } finally {
                lock.unlock();//解锁
            }
        }
    }
}

synchronized和Lock的对比
1、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有代码块锁和方法锁
3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多子类)
4、优先使用顺序:Lock > 同步代码块(已经进入方法体,分配了相应资源) > 同步方法(在方法体外)

1.5 线程通信

几种解决线程之间通信问题的方法

方法名作用
wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一对象上所有调用wait()方法的线程,优先级别高的线程优先调度
注意:均是Object类的方法。都只能在同步方法或者代码块中使用,否则抛出异常IllegalMonitorStateException

解决方法一:管程法 加一个缓冲区

//生产者消费者问题的第一种解决方式,使用缓冲区
//管程法
public class Test17 {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Producer(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Producer extends Thread{
    SynContainer container;//都需要的对象
    public Producer (SynContainer container){
        this.container = container;
    }

    //生产方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了第"+i+"只鸡");
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;//都需要的对象
    public Consumer (SynContainer container){
        this.container = container;
    }

    //消费方法

    @Override
    public void run() {

        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--->第"+container.pop().id+"只鸡");
        }
    }
}

//产品
class Chicken{
    int id;
    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{
    //产品
    Chicken[] chickens = new Chicken[10];
    //计数
    int count = 0;

    //生产者生产
    public synchronized void push(Chicken chicken){
        //如果容器满了就通知消费者消费
        if (count == chickens.length){
            //通知消费者消费,线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没有满就放入容器中
        chickens[count] = chicken;
        count++;

        //通知消费者可以消费了
        this.notifyAll();
    }


    //消费者消费
    public synchronized Chicken pop(){
        //判断,如果没有就通知生产者,消费者等待
        if (count == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有的话就能消费
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生产者
        this.notifyAll();

        return chicken;
    }
}

解决方法二:信号灯法 加一个标志位

//生产者消费者问题的第二种解决方式,使用标志位
//标志位法
public class Test18 {
    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生产者---演员
class Player extends Thread{
    TV tv = new TV();//都需要的对象

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                this.tv.play("表演快乐大本营");
            }else {
                this.tv.play("广告广告广告");
            }
        }
    }
}


//消费者---观众
class Watcher extends Thread{
    TV tv = new TV();//都需要的对象

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}


//产品---节目
class TV{
    String voice;//表演内容
    //演员表演,观众等待 T
    //观众观看,演员等待 F
    boolean flag = true;

    //表演
    public synchronized void play(String voice){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("演员表演了:"+voice);
        //通知观众观看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    //观看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了--"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

线程池
思路:提前创建好了多个线程,放如线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建、销毁,重复利用。类似于生活中的公共交通工具。
好处:
1、提高响应速度(减少了创建新线程的时间)
2、降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
3、便于线程管理(…)

线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
– void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
– < T >Future < T >submit(Callable< T > task):执行任务,有返回值,一般用来执行Callable
– void shutdown:关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//线程池
public class Test19 {
    public static void main(String[] args) {
        //创建服务,创建线程池
        //newFixedThreadPool需要传入线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭连接
        service.shutdown();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

二、JUC

2.1 前期准备

API开发文档
空的Maven项目

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

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.2 什么是JUC

java.util.concurrent
java.util.concurrent.atomic – 原子性
java.util.concurrent.locks – lock锁

回顾
Callable接口 是 java.util.concurrent 下的
Lock接口 是 java.util.concurrent.locks 下的

2.3 进程和线程

进程:是程序的一次执行过程。一个进程可以包含多个线程
Java默认有几个线程?两个 main 和 GC
线程:线程是CPU调度和执行的单位
Java中通过Thread、Runnable、Callable开启线程
java真的能开启线程吗?? 不能
是调用了本地的C++方法,无法直接操作硬件

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        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();

并发和并行
并发:多线程操作同一个资源 — 一个CPU,模拟多线程
并行:多个人一起行走 — 多个CPU,同时执行多个线程;线程池

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

并发编程的本质: 充分利用CPU资源

线程的状态???
NEW-----新生状态
RUNNABLE-----运行状态
BLOCKED-----阻塞状态
WAITING-----等待状态
TIMED_WAITING-----超时等待
TERMINATED-----死亡状态

wait和sleep的区别
1、来自不同的类,wait来自Object,sleep来自Thread
2、关于锁的释放,wait会释放锁,sleep不会
3、使用范围不同,wait必须在同步代码块中,sleep可以任何地方
4、是否需要捕获异常,wait不用,sleep必须捕获异常

2.4 Lock锁(重点)

传统的Synchronized锁

//买票的例子
/**
 * 真正的线程开发,即公司中的多线程开发
 * 线程就是一个单独的资源类,没有其他的附属操作
 * 传统操作方法:在操作资源的sale()方法前加关键字synchronized
 */
public class SaleTicketDemo1 {
    public static void main(String[] args) {
        //并发:多线程同时操作一个资源
        Ticket ticket = new Ticket();

        //使用Lambda表达式
        new Thread(() ->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

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

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

//资源类-只有属性和方法 不用实现Runnable接口  这才是真正的面向对象编程 OOP
class Ticket{
    private int nums = 30;

    public synchronized void sale(){
        if (nums>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+(nums--)+"张票,还剩"+nums);
        }
    }
}

Lock接口

实现类
ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock
在这里插入图片描述
公平锁:非常公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)

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

public class SaleTicketDemo2 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        //使用Lambda表达式
        new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"A").start();
        new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"B").start();
        new Thread(() ->{ for (int i = 0; i < 40; i++) ticket.sale(); },"C").start();

    }
}

class Ticket2{
    private int nums = 60;
    Lock lock = new ReentrantLock();

    public synchronized void sale(){
        lock.lock();//上锁
        try {//执行代码
            if (nums>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+(nums--)+"张票,还剩"+nums);
            }
        }finally {
            lock.unlock();//解锁
        }
    }
}

Synchronized和Lock的区别

1、Synchronized 是内置的关键字,Lock是一个Java类
2、Synchronized 无法判断锁的状态,Lock可以判断是否获取到锁
3、Synchronized 会自动释放锁,Lock必须要手动释放锁!!
4、Synchronized 线程1(获得锁阻塞)、线程2(一直等);Lock锁就不会一直等下去
5、Synchronized 可重入锁,不可中断,非公平;Lock可重入锁,可以判断锁,默认非公平(可以手动设置)
6、Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码块

锁是什么?如何判断锁的是谁?
见本文 2.6 8锁现象

2.5 生产者消费者问题

//生产者消费者问题
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        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();

    }
}

//资源类,只有属性和方法
class Data {
    private int num = 0;

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

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

上述代码问题:两个线程还能运行,多个线程的话运行就出错了!!产生虚假唤醒问题
因为使用了if语句,官方推荐使用while循环
在这里插入图片描述

JUC中的解决办法

对应关系
在这里插入图片描述
官方文档中的实例如下:
在这里插入图片描述

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

//生产者消费者问题
public class B {
    public static void main(String[] args) {
        Data2 data2 = new Data2();

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

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

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

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

    }
}

//资源类,只有属性和方法
class Data2 {
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await();等待
    //condition.signalAll();通知

    //判断等待,执行业务,通知别人
    public void increment() throws InterruptedException {
        lock.lock();
        try{
            while (num != 0){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            //通知其他线程,我+1完成
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try{
            while (num == 0){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"==>"+num);
            //通知其他线程,我-1完成
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

任何一个新技术的引入都不仅仅是实现了原来的方法,肯定有优势和补充

Condition 精准的通知和唤醒线程

上述代码执行的乱序的,想要按顺序执行A B C D
Condition可以实现,代码如下

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

/**
 * 实现 A B C 三个线程顺序执行
 */
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 lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int nums = 1 ;//1打印A  2打印B  3打印C

    public void printA(){
        lock.lock();
        try {//业务代码 判断等待,执行,唤醒指定线程
            while (nums != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAA");
            //精准唤醒
            nums = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {//业务代码 判断等待,执行,唤醒指定线程
            while (nums != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>BBB");
            nums = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {//业务代码 判断等待,执行,唤醒指定线程
            while (nums != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>CCC");
            nums = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

2.6 8锁现象

锁是什么?如何判断锁的是谁?
new的对象 class类

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.send();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);//JUC下的sleep方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class Phone{

    //send和call两个方法锁的是同一个 对象,谁先得到谁就先执行
    public synchronized void send(){
        try {
            TimeUnit.SECONDS.sleep(4);//JUC下的sleep方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发消息");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

问题:
1、send和call方法都为synchronized,一个对象,两个线程,先发消息还是先打电话???–先发短信再打电话
2、send中延迟4秒,call和send方法都为synchronized,一个对象,两个线程,先发消息还是先打电话???–先发短信再打电话
3、send方法为synchronized,hello方法为普通方法,一个对象,两个线程,先发消息还是先hello???–先hello再发短信
4、send和call方法都为synchronized,两个个对象,两个线程,先发消息还是先打电话???–先打电话再发消息
5、send和call方法都为static synchronized,一个对象,两个线程,先发消息还是先打电话???–先发消息后打电话
6、send和call方法都为static synchronized,两个对象,两个线程,先发消息还是先打电话???–先发消息后打电话
7、send方法为static synchronized,call方法为synchronized,一个对象,两个线程,先发消息还是先打电话???–先打电话再发短信
8、send方法为static synchronized,call方法为synchronized,两个对象,两个线程,先发消息还是先打电话???–先打电话再发短信

解释:
1.synchronized方法锁定对象是方法的调用者,即本例中 锁住的是phone这个对象
2.send和call两个方法锁的是同一个 对象,谁先得到谁就先执行
3.虽为同一个对象,但是普通方法不受锁的限制
4.两个对象时一个方法锁对应的对象,两把锁互不影响
5.static是静态方法,类一加载就有了,锁的是Class,,Phone3只有一个全局唯一的Class对象
6.phone和phone1两个对象都是Phone3的实例,Class只有一个,被锁了
7.静态同步方法锁的是Class类模板,同步方法锁的是调用者对象,两者锁的不是同一个东西
8.静态同步方法锁的是Class类模板,同步方法锁的是调用者对象,两者锁的不是同一个东西

2.7 集合类不安全

List类不安全
解决办法:
1、List< String > list = new Vector();–并发条件下安全—Vector底层使用了Synchronized
2、List< String > list = Collections.synchronizedList(new ArrayList<>());—工具类
3、List< String > list = new CopyOnWriteArrayList<>();----JUC包下的—底层应用了Lock

Set类不安全
解决办法:
1、Set< String> list = Collections.synchronizedSet(new HashSet<>());—工具类
2、Set< String> list = new CopyOnWriteArraySet<>();----JUC包下的—底层应用了Lock

Map类不安全
解决方法:
1、Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
2、Map<String,String> map = new ConcurrentHashMap<>();

2.8 Callable

实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦

注意点:
1、启动线程的Thread中只能传入Runnable,Callable通过传入FutureTask变为可传入的方法,来启动线程
2、结果会被缓存提高效率
3、get方法会产生阻塞,一般放到最后或者异步通信。

2.9 常用的辅助类

CountDownLatch
CyclicBarrier
Semaphore(信号量)

2.10 读写锁

2.11 阻塞队列

2.12 线程池(重点)

三大方法、七大参数、四种拒绝策略

//三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定的5个
ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的,遇强则强,遇弱则弱

三大方法的本质: ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

七大参数: ThreadPoolExecutor中有7个参数

int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//多产时间没有被调用会被释放
TimeUnit unit,//单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂,一般不用动
RejectedExecutionHandler handler//拒绝策略

四种拒绝策略

/**
 * 1.new ThreadPoolExecutor.AbortPolicy() //银行满了,还有人进来,直接拒绝,抛出异常
 * 2.new ThreadPoolExecutor.CallerRunsPolicy()  //哪里来的回哪去,如main方法中来的回main方法中
 * 3.new ThreadPoolExecutor.DiscardPolicy()  //队列满了丢掉任务,不会抛出异常
 * 4.new ThreadPoolExecutor.DiscardOldestPolicy()  //队列满了,尝试去和最早的竞争,也不会抛出异常!
 */

线程池的最大小如何设置?
CPU密集型和IO密集型
CPU密集型:几核的就是几,可以保持CPU高效— Runtime.getRuntime().availableProcessors()
IO密集型:> 判断程序中十分消耗IO的线程
一般为消耗IO线程的两倍

2.13 四大函数式接口(必须掌握)

新时代的程序员必须要会的:lambda表达式、链式编程、函数式接口、Stream流式计算

函数型接口
断定型接口
消费型接口
供给行接口

2.14 Stream流式计算

java.util.stream

2.15 ForkJoin

分支合并
并行执行任务,提高效率

2.16 异步回调

和Ajax一样,Java中也有异步回调
java.util.concurrent.CompletableFuture

unAsync(Runnable runnable)
返回一个新的CompletableFuture,它在运行给定操作后由运行在 ForkJoinPool.commonPool()中的任务 异步完成。

2.17 JMM

谈谈Volatile
1、保证可见性
2、不保证原子性
3、禁止指令重排

什么是JMM??
Java内存模型,不存在的东西,概念!约定!

关于JMM的一些同步约定:
1、线程解锁前,必须把共享变量 立刻 刷回主存
2、线程加锁前,必须读取主存中最新值到工作内存中
3、枷锁和解锁的是同一把锁

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

2.18 Volatile

1、保证可见性

2、不保证原子性

原子性:不可分割
线程A在执行任务的时候,是不能被打扰的,也不能被分割.要么同时成功,要么同时失败.
如果不使用lock和synchronized,怎么保证原子性???
: 使用原子类,解决原子性问题
原子类在java.util.concurrent.atomic包下,底层原理为CAS,直接和操作系统挂钩!在内存中修改值!
Unsafe类是很特殊的存在

3、禁止指令重排

什么是指令重排?
:你写的程序,计算机并不是按照你写的那样执行的
源代码–>编译器优化的重排–>指令并行的重排–>内存系统也会重排–>执行

int x = 1;
int y = 2;
x += 5;
y = 2 * x;

我们期望的是: 1234, 2134, 1324

volatile可以避免指令重排: 内存屏障: CPU指令, 作用:

  1. 保证特定的操作的执行顺序
  2. 可以保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)
    在这里插入图片描述
    Volatile 是可以保证可见性, 不能保证原子性,由于内存屏障可以避免指令重排的现象产生 !

2.19 单例模型

饿汉式

public class HungryMan {

    private static HungryMan HUNGRYMAN = new HungryMan();

    private HungryMan(){
    }

    public static HungryMan getInstance(){
        return HUNGRYMAN;
    }
}

懒汉式

//单线程安全
public class LazyMan {

    private static LazyMan lazyMan = null;

    private LazyMan(){
    }

    public static LazyMan getInstance(){
        if(lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

DCL(double-checked locking)懒汉式,volatile

import java.lang.reflect.Constructor;

public class LazyManThread {

    private static volatile LazyManThread lazyManThread = null;

    private static boolean isExist = false;

    private LazyManThread() {
        synchronized (LazyManThread.class) {
            if (!isExist) {
                isExist = true;
            } else {
                throw new RuntimeException("禁止使用反射创建该对象");
            }
        }

    }

    private LazyManThread(int a){
        synchronized (LazyManThread.class){
            if(lazyManThread != null){
                throw new RuntimeException("禁止使用反射创建该对象");
            }
        }
    }

    public static LazyManThread getInstance() {
        //if只会判断一次,当两个线程同时判断时一个线程就会在同步代码块中等待
        if (lazyManThread == null) {
            //不直接使用同步的原因,提高执行效率
            synchronized (LazyManThread.class) {
                if (lazyManThread == null) {
                    lazyManThread = new LazyManThread();
                }
            }
        }

        /**
         * 由于对象创建不是原子性操作
         * 1. 分配内存空间
         * 2. 使用构造器创建对象
         * 3. 将对象指向内存空间
         */
        /**
         * 可能会发生指令重排
         * 123
         *
         * 132
         *
         * 这是就需使用volatile关键字来防止指令重排
         */
        return lazyManThread;
    }


    public static void main(String[] args) throws Exception {
//        LazyManThread instance = LazyManThread.getInstance();

        Constructor<LazyManThread> declaredConstructor = LazyManThread.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyManThread lazyManThread = declaredConstructor.newInstance();
        LazyManThread instance = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(lazyManThread);
    }
}

静态内部类

public class LazyMan1 {

    private LazyMan1() {}

    public static final LazyMan1 getInstance(){
        return innerClass.LAZY_MAN_1;
    }

    public static class innerClass {
            private static final LazyMan1 LAZY_MAN_1 = new LazyMan1();
    }
}

单例不安全,可以通过反射来破坏

枚举

import java.lang.reflect.Constructor;

public enum EnumSingle {

    INSTANCE;


    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{

    public static void main(String[] args) throws Exception {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance1 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance1);
    }
}

枚举实现的单例是安全的

枚举最终反编译代码

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.czp.single;

public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/czp/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

2.20 深入理解CAS

什么是CAS

import java.util.concurrent.atomic.AtomicInteger; 

public class CASDemo { 

	// CAS compareAndSet : 比较并交换! 
	public static void main(String[] args) { 
		AtomicInteger atomicInteger = new AtomicInteger(2020); 
		
		// 期望、更新 
		// public final boolean compareAndSet(int expect, int update) 
		// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语! 
		System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
		System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement() 
		System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
		System.out.println(atomicInteger.get()); 
	} 
}

Unsafe类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题

CAS中的ABA问题(狸猫换太子)
在这里插入图片描述

import java.util.concurrent.atomic.AtomicInteger; 

public class CASDemo { 
	// CAS compareAndSet : 比较并交换! 
	public static void main(String[] args) { 
	
		AtomicInteger atomicInteger = new AtomicInteger(2020); 
		// 期望、更新 
		// public final boolean compareAndSet(int expect, int update) 
		// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语! 
		
		// ============== 捣乱的线程 ================== 
		System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
		System.out.println(atomicInteger.get()); 
		System.out.println(atomicInteger.compareAndSet(2021, 2020)); 
		System.out.println(atomicInteger.get()); 
		
		// ============== 期望的线程 ================== 
		System.out.println(atomicInteger.compareAndSet(2020, 6666)); 
		System.out.println(atomicInteger.get()); 
	} 
}

2.21 原子引用

解决ABA 问题,引入原子引用! 对应的思想:乐观锁!

带版本号的原子操作

import java.util.concurrent.TimeUnit; 
import java.util.concurrent.atomic.AtomicStampedReference; 

public class CASDemo { 

	//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题 
	
	// 正常在业务操作,这里面比较的都是一个个对象 
	static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1); 
	
	// CAS compareAndSet : 比较并交换! 
	public static void main(String[] args) { 
		new Thread(()->{ 
			int stamp = atomicStampedReference.getStamp(); // 获得版本号 
			System.out.println("a1=>"+stamp); 
			try {
				TimeUnit.SECONDS.sleep(1); 
			} catch (InterruptedException e) { 
				e.printStackTrace(); 
			}
			atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); 
			System.out.println("a2=>"+atomicStampedReference.getStamp()); 
			System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); 
			System.out.println("a3=>"+atomicStampedReference.getStamp()); 
			
		},"a").start(); 
		
		// 乐观锁的原理相同! 
		new Thread(()->{ int stamp = atomicStampedReference.getStamp(); 
		
			// 获得版本号 
			System.out.println("b1=>"+stamp); 
			try {
				TimeUnit.SECONDS.sleep(2); 
			} catch (InterruptedException e) { 
				e.printStackTrace(); 
			}
			
			System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1)); 
			System.out.println("b2=>"+atomicStampedReference.getStamp()); 
			
		},"b").start();
	}
}

注意:
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实
例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

在这里插入图片描述

2.22 各种锁的理解

2.22.1 公平锁、非公平锁

2.22.2 可重入锁

2.22.3 自旋锁

2.22.4 死锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值