JAVAEE-多线程(3)

news2024/11/20 3:25:19

volatile关键字

volatile禁止了编译器优化,还可以禁止指令重排序,避免了直接读取CPU寄存器中的缓存数据,而是每次都重新读内存。

因为编译器每次正常执行都是把主内存的数据加载到工作内存中,再进行计算处理。volatile即使保证每次读取内存都是真正从主内存中读取的。(工作内存并不是真正的内存,主内存才是真正的内存)

例如可以用于禁止编译器优化,从而处理之前JAVAEE多线程2中编译器优化而导致的线程调用优化而导致的进程结果运行错误。

public class L107 {
    static class Counter{
        volatile public int count = 0;
    }
    public static void main(String[] args){
        Counter counter = new Counter();

        Thread t = new Thread(()->{
            while (counter.count == 0){

            }
        });
        t.start();

        Thread t2 = new Thread(()->{
            System.out.println("请输入一个数字");
            Scanner scanner = new Scanner(System.in);
            counter.count=scanner.nextInt();
        });
        t2.start();
    }
}

 这个时候进程可以正常退出。

tip:要解决上述问题,根源在于短时间多次重复读取不变的数据而导致的编译器优化。因此要解决的话也可以从短时间这个方面入手,去增加每次读取间隔。

public class L107 {
    static class Counter{
        public int count = 0;
    }
    public static void main(String[] args){
        Counter counter = new Counter();

        Thread t = new Thread(()->{
            while (counter.count == 0){
                try {
                    Thread.sleep(1000);
                    //在线程进行休眠,间隔一秒读取一次内存,避开编译器自动优化
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("执行结束");
        });
        t.start();

        Thread t2 = new Thread(()->{
            System.out.println("请输入一个数字");
            Scanner scanner = new Scanner(System.in);
            counter.count=scanner.nextInt();
        });
        t2.start();
    }
}

volatile起到的效果是保证内存可见性,并不保存原子性。

针对一个线程读,一个线程修改,这个场景是合适的。如果是两个线程修改则无能为力了。

如下图

package threading;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 14:56
 */
public class L1071 {
    static class Counter{
        volatile public int count = 0;

        public void increase(){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(counter.count);
    }
}

即使修饰了count,面对t1和t2两个线程的加和仍然算不到预期的十万这个结果。

wait和notify来调配线程执行顺序

wait操作做了三件事情:

1.释放当前锁。(得先加上锁才能谈释放,因此wait和加锁操作密不可分)

2.进行等待通知。

3.满足一定条件的时候(别人调用notify),被唤醒,然后尝试重新获取锁。

notify也要包含在synchronized里面,因为线程1没有释放锁的话,线程2也就无法调用到notify(因为锁阻塞等待),线程1调用wait,在wait里面就释放锁了,这个时候虽然线程1阻塞synchronized里面,但是线程1的锁已经释放了。

package threading;

import java.util.Objects;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 15:15
 */
public class L1072 {
    public static void main(String[] args) {
        Object object = new Object();
        System.out.println("wait之前");
        synchronized (object){
            try {
                object.wait();
                //wait是object的子类
                //
                //线程执行到wait就会发生阻塞,直到另一个线程调用notify把这个wait唤醒才会继续往下走。
                //在wait里面执行的这个时候的锁已经解锁了,其他线程可以正常获取到这个锁
                
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("wait之后");
    }

}

如果有t1 t2两个线程

t1加锁后,在执行部分逻辑到wait时。(t2也想加锁但是要等t1执行wait解锁)

wait解开t1的锁,t2再进行加锁,执行部分逻辑直到notify。

然后跳转到t1的wai阻塞t部分,唤醒t1并重新加锁

可能直到t2继续执行,并成功解锁后t1才会加锁成功。

注意:

加锁的对象和调用wait的对象需要是同一个对象。

wait的对象和notify的对象也得是同一个对象

如下示例:

package threading;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 15:50
 */
public class L1073 {
    public static void main(String[] args) {
//        专门准备一个对象,保证和等待通知的是同一个对象
        Object object = new Object();

        //第一个线程进行wait
        Thread t1 = new Thread(()->{
           while (true){
               synchronized (object){
                   System.out.println("wait之前");
                   try {
                       object.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
                   //这里写的代码一定是在notify之后执行的
                   System.out.println("wait之后");
               }

           }
        });
        t1.start();

        //第二个i=线程进行notify
        Thread t2 = new Thread(()->{
            while (true){
                synchronized (object){
                    System.out.println("notify之前");

                    //这里写的代码一定是在wait唤醒之前执行的
                    object.notify();
                    System.out.println("notify之后");
                }
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t2.start();

    }
}

 如果t2先执行了notify,t1后执行了wait,就直接错过了。调用Notify不会有副作用,就会一直等下去。

还有一个notifyAll 可以唤醒所有的wait,唤醒的wait需要重新竞争锁。过程仍然是串行的

多线程案例

单例模式

单个实例instance对象,某个类有且只有一个实例(为需求决定的)

当属性变成类属性的时候,此时就已经是”单个实例“

import java.security.Signature;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 17:01
 */
//把这个类作为单例类,要求里面只有一个实例
class Singleton{
    private static Singleton instance = new Singleton();
    //此时instance就是这个类唯一实例
    public  static Singleton getInstance(){
        return instance;
    }
    //把构造方法设为private,此时在类外面,就无法继续new实例了。
    private Singleton() {

    }
}
public class L1074 {
    public static void main(String[] args) {
        //如果代码这么写就违背了初衷,应该禁止这个类在类外部被new。
        //Singleton instance2 = new Singleton();
        //因此需要把private设置为私有的
        Singleton instance = Singleton.getInstance();
    }
}

懒汉模式

package threading;

import java.security.Signature;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 17:15
 */

class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new  SingletonLazy();
            //在这里真正创建实例,首次调用getinstance的时候触发
            //后续调用的时候instance不为空直接返回。
        }
        return instance;

    }
}
public class L1075 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }

}

单例模式只涉及读,而懒汉模式涉及到修改,相较于单例模式更加不安全。

要怎么让上述代码保证线程安全呢,就需要通过加锁把多个操作打包成一个原子操作。

package threading;

import java.security.Signature;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 17:15
 */

class SingletonLazy{
    private volatile static SingletonLazy instance = null;
    //加上violatile来禁止指令重排序
    public static SingletonLazy getInstance(){
        if(instance == null ){
            //这个if判定是否要加锁。
            synchronized (SingletonLazy.class){

                //在最外层给最开始的加载数据就加锁,但是当线程安全的时候,无法避免还是要进行加锁这个操作,稍显繁琐。因此还可以加一层判断条件
                if(instance == null){
                    //这个if判断是否要创建实例。
                    instance = new  SingletonLazy();
                }
                //在这里真正创建实例,首次调用getinstance的时候触发
                //后续调用的时候instance不为空直接返回。
            }
        }
        return instance;

    }
}
public class L1075 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }

}

但是还是存在一个问题,有两线程同时调用了getinstance(),第一个线程拿到锁,进入第二层if开始new对象,new操作本身可以粗略分为三个步骤:

1.申请内存

2.调用构造方法,来初始化实例

3.把内存首地址赋值给instance引用

这个场景,编译器就可能会进行指令重排序这个隐藏的线程安全隐患。new的2,3步在单线程角度下是可以调换顺序的,因为效果完全一样。如果这个时候触发指令重排序,按照132顺序执行,就会得到一个不完全的对象,只有内存但是内存数据无效。因此t2线程在调用getinstance()的时候就会认为这个instance非空从而直接返回instance,后续可能就会针对instance来进行解引用操作(使用里面的属性,方法)

因此需要在加上violatile来禁止指令重排序,同时如果有更多的线程,有的线程在修改,也会导致内存可见性问题,更应该加上volatile。

阻塞队列(先进先出的特殊队列)

和普通队列相比,阻塞队列有:

1.线程安全特点

2.带有阻塞功能

 a.如果队列慢,继续入队列,入队列操作就会阻塞,直到队列不满,入队列才能完成。

 b.如果队列空,继续出队列,出队列操作也会阻塞,直到队列不空,出队列才会完成。

使用阻塞队列,可以有利于代码解耦合(降低两个代码模块的关联程度)

同时可以削峰填谷。

package threading;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 17:58
 */
public class L1076 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(100);

        //带有阻塞功能的put
        blockingQueue.put(1);
        blockingQueue.put(2);
        blockingQueue.put(3);

        //出队列
        Integer ret = blockingQueue.take();
        System.out.println(ret);
        ret = blockingQueue.take();
        System.out.println(ret);
        ret = blockingQueue.take();
        System.out.println(ret);

        //如果多取一个出来,程序就会阻塞。
        ret = blockingQueue.take();
        System.out.println(ret);
    }
}

自己实现阻塞队列:

1.先实现普通队列

2.加上线程安全

3.加上阻塞的实现

package threading;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-07
 * Time: 18:08
 */
class MyBlockingQueue{
    private int[] items = new int[1000];
    private volatile int head =0;
    private volatile int tail =0;
    private volatile int size =0;

    //入队列
    public void put(int elem) throws InterruptedException {
    //判断队列是否满了
        while (size>=items.length){
            this.wait();
        }
        //进入插入操作,把elem放到items里面,放到tail指向的位置。
        items[tail] = elem;
        tail++;
        if(tail>=items.length){
            tail=0;
        }
        size++;
        this.notify();
        //入队列成功就唤醒判断队列是否为空的wait
    }

    //出队列
    public Integer take() throws InterruptedException {
    //判断队列是否为空,为空则不能出队列。
        while (size==0){
            //判断条件,队列为空就阻塞
            this.wait();
        }
        //队列非空,进行取元素操作。
        int ret = items[head];
        head++;
        if(head>=items.length){
            head = 0;
        }
        size--;
        this.notify();

        return ret;
    }
}
//在队列中,同一时刻不会出现即是空又是满的情况,要么是put阻塞,要么是take阻塞
public class L077 {
    public static void main(String[] args) {

    }
}

还差一些未补完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147589.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

梦想cms1.4代码审计

目录 一、环境配置 二、代码审计 1、后台漏洞 &#xff08;1&#xff09;BookAction.class.php &#xff08;2&#xff09;BackdbAction.class.php任意文件删除 2、前台漏洞 &#xff08;1&#xff09;TagsAction.class.php &#xff08;2&#xff09;BookAction.cla…

深入理解数据结构 —— 树状数组

什么是树状数组 我们知道&#xff0c;前缀和数组能解决任意一段区间的累加和问题 但这建立在数组中的元素不发生变化的情况&#xff0c;如果可以修改原始数组中的某个元素&#xff0c;为了让前缀和数组正确&#xff0c;就需要在前缀和数组中修改该元素位置后面的所有的数&…

数组模拟实现单链表、双链表、栈、队列

文章目录 前引 一、数组模拟实现单链表 1、1 数组模拟的单链表解析 1、2 数组模拟实现单链表例题 二、数组模拟实现双链表 2、1 数组模拟实现双链表解析 2、2 数组模拟实现双链表例题 三、数组模拟实现栈 3、1 数组模拟实现栈解析 3、2 数组模拟实现栈例题 四、数组模拟实现队…

【C++学习】基础语法(一)

1、背景知识 1.1 什么是C C语言是结构化和模块化的语言&#xff0c;长用于处理较小规模的程序&#xff1b;对于规模较大、问题复杂的程序&#xff0c;则需要高度的抽象和建模&#xff0c;此时C语言不合适处理这类问题。为了解决此类影响软件的问题&#xff0c;20世纪80年代&am…

Python | Matplotlib | 不完全总结

本文对 Matplotlib 的用法进行不完全总结。 更新&#xff1a; 2023 / 1 / 4 Python | Matplotlib | 不完全总结ImageMagick导库画布创建多子图动图2D柱状图基本&#xff1a;水平 / 垂直柱形主题&#xff1a;颜色、文字、网格线动图线图基本动图3D柱状图基本线图动图参考链接Im…

Web文件操作:上传与下载

文件上传与下载文件上传文件上传的实现表结构设计UploadFileServiceImpl的实现上传文件遇到的问题与解决文件下载文件上传 文件上传的表单中&#xff0c;需要注意的三个地方&#xff1a; 1.表单的请求方式必须为post&#xff1b; 2.表单域必须有file类型&#xff1b; 3.表单的e…

活动星投票奋斗青春,使命必达网络评选微信的投票方式线上免费投票

“奋斗青春&#xff0c;使命必达”网络评选投票_如何进行投票推广_参与投票活动_小程序的投票发展现在来说&#xff0c;公司、企业、学校更多的想借助短视频推广自己。通过微信投票小程序&#xff0c;网友们就可以通过手机拍视频上传视频参加活动&#xff0c;而短视频微信投票评…

英伟达528.02驱动发布支持4070 Ti!GFE新增9款游戏

自GTX 4070 Ti显卡发售后&#xff0c;英伟达便随即发布了支持该新显卡的Game Ready 528.02驱动&#xff0c;同时为《战意》和《达喀尔沙漠拉力赛》两款新游戏带来DLSS 3的支持&#xff0c;DLSS 3的队伍再度壮大&#xff01; 驱动人生现已支持英伟达Game Ready 528.02驱动&…

围绕http请求头中Referer展开的一些知识

1. 什么是referer&#xff1f; <点击以获取跳转信息 >跳转过去记得按一下f12点击网络请求详情&#xff0c;再刷新一下&#xff0c;就可以看见referer字段&#xff1a; 当我们尝试在浏览器内部直接输入这熟悉的网址时&#xff0c;此时刷新后则是这样一番景象&#xff1…

C++类和对象的基本概念

目录 1.c和c中struct的区别 2.类的封装 3.类的访问权限 1.c和c中struct的区别 c语言中结构体中不能存放函数,也就是数据(属性)和行为(方 法)是分离的 c中结构体中是可以存放函数的,也就是数据(属性)和行为 (方法)是封装在一起的 #define _CRT_SECURE_NO_WARNINGS #include …

基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码

face_login 代码下载地址&#xff1a;基于Python tensorflow机器学习的人脸识别登陆系统源码、人脸注册系统源码 介绍 本项目基于tensorflow机器学习&#xff0c;实现web端人脸识别登陆&#xff0c;人脸注册。 提供手机端页面(face_login_app)和网页端页面(vue_element-adm…

JUC并发编程学习笔记(六)线程池及分支合并框架

10 ThreadPool 线程池&#xff08;重点&#xff09; 10.1 线程池简介 回顾以前的连接池概念 连接池是创建和管理一个连接的缓冲池的技术&#xff0c;这些连接准备好被任何需要它们的线程使用 线程池&#xff08;英语&#xff1a;thread pool&#xff09;&#xff1a;一种线程…

实时数仓,为什么不可代替?

什么是实时数据仓库&#xff1f;它有哪些不可替代之处&#xff1f; 大数据时代中&#xff0c;数据仓库解决了商业智能分析过程中的数据管理问题&#xff0c;但是存在烟囱式、冗余高的弊端 随着商业智能的兴起和数据时代的到来&#xff0c;越来越多的企业开始汇总、整合和分析自…

ArcGIS基础实验操作100例--实验62点、线、面状符号

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验62 点、线、面状符号 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

C/C++中二级指针传递参数【个人遇到内存值发生改变现象的记录及相关修正方法】

目录 0、前言 1、二级指针传参奇怪现象 2、分析 3、解决方法 0、前言 在c/c中&#xff0c;时常会使用到主调函数通过参数去获取被调函数中的数值情况。针对这种情况&#xff0c;我前面也写过C/C主调函数从被调函数中获取&#xff08;各种类型&#xff09;数据内容方式的梳理…

【ONE·R || 两次作业(一):R基础数据处理】

总言 两次作业汇报&#xff1a;其一。    文章目录总言1、作业一&#xff1a;1.1 、任务一&#xff1a;各项数据建立1.2 、任务二&#xff1a;去除缺失值1.3 、任务三&#xff1a;返回性别为女生&#xff0c;年龄<20的学生及成绩1.4、 任务四&#xff1a;统计性别为女生&a…

【Python百日进阶-数据分析】Day149 - plotly直方图:go.histogram()

文章目录4.2 利用 go.Histogram 的直方图4.2.1 基本直方图4.2.2 归一化直方图4.2.3 水平直方图4.2.4 叠加直方图4.2.5 堆叠直方图4.2.6 风格直方图4.2.7 直方图条形文本4.2.8 累积直方图4.2.9 指定聚合函数4.2.10 自定义分箱4.2.11 在直方图之间共享 bin4.2.12 按类别顺序排序直…

深度学习(一)-环境安装

前言&#xff1a; 最近电脑重装了下系统&#xff0c;然后所有环境啥的都得重新配置一遍&#xff0c;刚好趁着这个时间记录下整个环境的配置过程 注意&#xff1a;本文记录的仅为window系统的配置过程! 一、Anaconda安装及相关配置 Anaconda下载地址&#xff0c;根据需要选择需…

TypeScript 中 Class incorrectly implements interface 错误

当一个类在没有指定接口上定义的所有属性和方法的情况下实现接口时&#xff0c;会发生错误“Class incorrectly implements interface”。 要解决该错误&#xff0c;需要确保定义并键入接口的所有必需属性和方法。 下面是产生上述错误的示例代码 interface Employee {id: num…

Linux学习记录——유 gcc/g++基础知识

文章目录一、程序翻译二、gcc使用1、-o2、预处理-E3、编译-S4、汇编-c5、链接三、库四、库的部分实际操作五、Linux项目自动化构建工具 make/Makefile1、规则一、程序翻译 C语言中&#xff0c;写出代码后&#xff0c;编译器会经过四个阶段才会生成可执行文件。 预处理&#x…