学习笔记-JUC

1.JUC概述

1.什么是JUC?

在Java中,JUC是关于线程的。JUC是java。util。concurrent工具包。

2.线程和进程

线程与进程

进程:资源分配的最小单位
线程:程序执行的最小单位

线程的状态(5种)

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

wait和sleep

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

并发和并行

管程

2.Lock接口

复习synchronized

LOCK接口不让等待的线程一直无期限地等待,可以通过设置一定的时间或者是中断处理机制
讲解LOCK接口之前涉及另一个锁机制synchronized

创建线程的多种方式

  • 继承Thread类(用的少,Java是单继承,很珍贵)
  • 实现Runnable接口
  • 使用Callable接口
  • 使用线程池

结合synchronized多线程的编程步骤主要是:(高内聚低耦合)

synchronized作用范围

synchronized是Java的关键字,是一种同步锁。修饰一下对象:
1.修饰一个代码块

synchronized(this) {

}

2.修饰一个方法
3.修改一个静态方法
4.修改一个类

synchronized卖票例子

多线程编程步骤

1.创建资源类,在资源类创建属性和操作方法
2.创建多个线程,调用资源类的操作方法

package com.tabjin.juc.sync;

/**
 * @author Tabjin
 * @program JUC
 * @description synchronized案例
 */

// 1.创建资源类,定义属性和操作方法
class Ticket {
    // 票数
    private int number = 30;

    // 操作方法:卖票
    public synchronized void sale() {
        // 判断:是否有票
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖出 " + (number--) + "剩下:" + number);
        }
    }
}


public class SaleTicket {
    // 2.创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        // 创建Ticket对象
        Ticket ticket = new Ticket();
        // 创建3个线程
        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();
    }
}

代码执行的时候,AA、BB与CC三个线程执行堆叠在一起,这是因为不同线程不同对象启动执行,都可以访问。

Tip
具体创建线程的方法是new Thread(new Runnable({ //重写run方法},线程名)
或者可以通过lambda表达式这样创建

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

Lock接口

Lock接口介绍

synchronized的上锁和解锁是由其自动完成,实际上上锁和解锁也可以使用Lock接口手动控制。
Lock为锁和等待条件提供一个框架的接口和类,不同于内置同步和监视器, LOCK是类,可通过类实现同步访问,多个接口实现类:可重入锁等

  • Lock与synchronized区别
    • Lock不是Java内置的,synchronized是Java关键字。Lock是一个类,通过这个类可以实现同步访问。
    • Lock与synchronized有一个非常大的不同,采用synchronized不需要用户去手动释放锁,当syncchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放锁的占用;而Lock则必须要用户手动释放锁,否则可能出现死锁。
    • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁;而Lock发生异常时,若没有主动通过unlock()去释放锁,则很可能造成死锁,因此使用Lock时需要在finally块中释放锁。
    • Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能响应中断
    • 通过Lock可以知道有没有成功获取锁,而synchronized做不到
    • Lock可以提高多个线程进行读操作的效率。

Lock实现可重入锁

Lock的编程步骤同synchronized

1.创建资源类,在资源类中船舰属性和操作方法
2.创建多个线程,调用资源类的操作方法

可重入锁的代码定义private final ReentrantLock lock = new ReentrantLock(true);
上锁lock.lock();
解锁lock.unlock();
上锁与解锁中的代码如果出现异常,解锁会执行不了,所以最好加try..finally

package com.tabjin.juc.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Tabjin
 * create at 2022/3/9 09:48
 * @program JUC
 * @description
 */

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

    // 创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    // 操作方法:卖票
    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) {
        // 2.创建多个线程,调用资源类的操作方法
        LTicket ticket = new LTicket();

        // 创建3个线程
        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();
    }
}

image-20220309100203681

start不一定是即时创建的,start有个native,交给操作系统执行。

Lock常用接口

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

lock()方法用来获取锁

  • 如果锁已被其他线程获取,则进行等待
  • 发生异常不会自动解锁,需用在 try{}catch{}块中进行
lock.lock();
try{
	//处理任务
}catch(Exception ex){
}finally{
	lock.unlock(); //释放锁
}

Condition 类也可以实现等待/通知模式

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

notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知, Condition 比较常用的两个方法:

await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行
signal()用于唤醒一个等待的线程
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法
ReentrantLock可重入锁

ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个方法:readLock() writeLock()用来获取读锁和写锁
writeLock();来获取读锁
readLock();获取写锁

public interface ReadWriteLock {
 /**
 * Returns the lock used for reading.
 *
 * @return the lock used for reading.
 */
 Lock readLock();
 /**
 * Returns the lock used for writing.
 *
 * @return the lock used for writing.
 */
 Lock writeLock();
}

3.线程间通信

线程间通信的模型有两种:共享内存和消息传递
线程间的通信具体步骤:(涉及上中下部)

  1. 创建资源类,在资源类中船舰属性和操作方法
  2. 在资源类操作方法:判断、操作、通知
  3. 创建多个线程,调用资源类的操作方法
  4. 防止虚拟唤醒问题

image-20220309101332312

1.synchronized实现线程间通信

操作线程的时候,等待线程使用wait()
通知另外的线程操作用notify()notifyAll()
假设有两个线程,该线程在执行过程中,判断值(不是该值等待,让其他线程抢),操作值,通知另外一个线程的调度。

通过使用两个线程对0这个值操作,一个线程加1,一个线程减1,交替实现多次。

package com.tabjin.juc.sync;

/**
 * @author Tabjin
 * @program JUC
 * @description
 */

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

    // +1操作
    public synchronized void increment() throws InterruptedException {
        // 第二步 判断 干活 通知
        if (number != 0) {// 判断number是否是0,如果不是0,等待
            this.wait();
        }
        // number是0,+1操作
        number++;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        // 通知其他线程
        this.notify();
    }

    // -1操作
    public synchronized void decrement() throws InterruptedException {
        if (number != 1) {// 判断number是否是1,如果不是1,等待
            this.wait();

        }
        // number是1,-1操作
        number--;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        // 通知其他线程
        this.notify();
    }
}

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

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

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

image-20220309103116149

2.虚假唤醒问题

如果使用多个线程,添加额外两个线程,且操作要依次执行

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

在这里插入图片描述

主要是虚拟唤醒导致:如果一个线程执行完毕后,通知其他线程,该线程又进入等待睡眠,可能会因为某些原因被唤醒后,if结构的语句就不会判断了,一直往下执行,所以需要将if换成while结构,每次都判断。因为wait在哪里睡眠就在哪里被唤醒,结果被某个异常唤醒了后回不去了,if结构不会在判断了,需要更改为while。
主要问题出现在下面的代码

if (number != 0) {// 判断number是否是0,如果不是0,等待
    this.wait();// 在哪里睡,在哪里醒
}

// 需要将if改为while,每次都wait
while (number != 0) {// 判断number是否是0,如果不是0,等待
    this.wait();// 在哪里睡,在哪里醒
}

image-20220309104849035

2.Lock实现线程间通信

使用lock先要创建锁的对象以及通知的对象
放置在资源类中

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

上锁lock.lock();
解锁lock.unlock();
以下都为condition类:
唤醒所有等待的线程signalAll(),带上类名condition.signalAll();
唤醒一个等待线程signal(),带上类名,condition.signal();
造成当前线程在接到信号或者被中断之前一直处于等待状态await(),带上类名,condition.await();

同样是上面的案例题目换成lock
比如一个加1操作

package com.tabjin.juc.lock;

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

/**
 * @author Tabjin
 * @program JUC
 * @description Lock实现线程间通信
 */

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

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

    // +1操作
    public void increment() throws InterruptedException {

        // 上锁
        lock.lock();

        try {
            // 第二步 判断 干活 通知
            // 判断
            while (number != 0) {// 判断number是否是0,如果不是0,等待
                condition.await();
            }
            // 干活 number是0,+1操作
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            // 通知其他线程
            condition.signalAll();
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    // -1操作
    public void decrement() throws InterruptedException {
        lock.lock();

        try {
            while (number != 1) {// 判断number是否是1,如果不是1,等待
                condition.await();
            }
            // number是1,-1操作
            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.increment();// +1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

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

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

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

4.线程间定制化通信

所谓定制化通信,需要让线程进行一定的顺序操作。

案列:启动三个线程,按照如下要求:
AA线程打印5次,BB线程打印10次,CC线程打印15次,一共进行10轮。

具体思路:
每个线程添加一个标志位,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志位的线程。

创建一个可重入锁private Lock lock = new ReentrantLock();
分别创建三个开锁通知private Condition c1 = lock.newCondition();

具体资源类中的A线程代码操作
上锁,(执行具体操作(判断、操作、通知),解锁)放于tryfinally

image-20220309113049686
package com.tabjin.juc.lock;

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

/**
 * @author Tabjin
 * @program JUC
 * @description 线程间定制化通信
 */
// 第一步 创建资源类
class ShareResource {
    // 标志位
    private int flag = 1;// 1 A  2B  3C
    // 创建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;// 修改标志位
            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 = 0; i <= 10; i++) {
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

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

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

image-20220309114553331

5.集合的线程安全

在讲解线程安全的之前,先讲解线程不安全的实例
其中的代码主要涉及一些知识点
1.java中Arrays.toString()详细分析(全)https://blog.csdn.net/weixin_47872288/article/details/116782849
2.java之UUID.randomUUID().toString()详细解析(全)https://blog.csdn.net/weixin_47872288/article/details/119573423
以及涉及更细微的函数3.javaSE从入门到精通(全)https://blog.csdn.net/weixin_47872288/article/details/119009945?spm=1001.2014.3001.5502

1.集合线程不安全演示

2.解决方案-Vector

3.解决方案-Collections

4.解决方案-CopyOnWriteArrayList

6.多线程锁

7.

8.JUC强大的辅助类

9.ReentranReadWritedLock读写锁

10.BlockingQueue阻塞队列

11.ThreadPool线程池

12.分支合并框架

13.异步回调

posted @   Tabjin  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示