当前位置: 首页 > news >正文

多线程(二)

文章目录

    • 1.线程不安全问题
    • 2.synchronized
    • 3.volatile
    • 4.wait()方法和notify()方法

1.线程不安全问题

public class demo2 {
  public  static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread (()->{
            for (int i = 0; i <50000 ; i++) {
                increase();
            }
        });
        Thread thread2 = new Thread (()->{
            for (int i = 0; i <50000 ; i++) {
                increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
    public static void increase(){
        count++;
    }
}

在这里插入图片描述
出现线程安全问题
原因:
1.线程是抢占执行的(执行顺序是随机的)
线程的执行是人为无法改变的,完全由CPU自己调度,抢占式线程是造成线程不安全的主要原因.
2.共享资源的并发性
多个线程修改同一个变量会引发线程不安全
当多个线程同时访问和修改同一个共享资源(如变量、对象、数据结构等)时,如果没有适当的同步机制,就可能导致数据不一致。
3.非原子性
某些操作(如复合操作)并不是原子性的,即它们可以被中断。例如,读取一个变量、修改它并写回的过程可能被其他线程打断,导致数据不一致。
4.可见性问题
可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。 由于 CPU 缓存的存在,线程对共享变量的修改可能不会立即刷新到主内存中,导致其他线程读取到过时的数据。
5.指令重排序
为了提高性能,编译器和处理器可能会对指令进行重排序。 指令重排序可能会导致程序的执行顺序与代码的编写顺序不一致,从而导致线程安全问题。

2.synchronized

synchronized 关键字: 这是最基本的同步机制。 它可以用于修饰方法或代码块。
同步方法: 当一个线程进入 synchronized 方法时,它会获取该方法所属对象的锁。 其他线程必须等待该线程释放锁才能进入该方法。
加上synchronized关键字此代码逻辑正确

  1. 同步方法
    使用 synchronized 修饰一个实例方法,表示该方法在同一时间只能被一个线程访问。
    没有加synchronized关键字
    在这里插入图片描述
    加上synchronized关键字
    在这里插入图片描述

  2. 同步代码块
    使用 synchronized 修饰代码块,可以指定一个对象作为锁,这样可以提高程序的并发性,因为不同的代码块可以使用不同的锁
    在这里插入图片描述

  3. 静态同步方法
    如果使用 synchronized 修饰静态方法,那么锁定的是类的 Class 对象,而不是实例对象。这意味着同一时间只有一个线程可以访问该类的所有静态同步方法。
    在这里插入图片描述
    只给一个线程加锁也会出现线程安全问题
    在这里插入图片描述
    没有出现锁竞争线程安全出现问题。
    线程获取锁:
    1.若只有一个线程A可以获取锁,不会产生锁竞争
    2.线程A与线程B不是竞争同一把锁,不会产生锁竞争
    3.线程A与线程B竞争同一把锁时,产生锁竞争,谁先抢到就执行自己逻辑,另外一线程阻塞等待,直到待持锁的完成,再参与锁竞争

synchronized特性:
1.通过锁保证了原子性
2.通过串行避免线程读取到过时的数据
3.不保证有序性(不会禁止指令重排序)

3.volatile

  1. volatile 关键字
    volatile 能保证内存可⻅性
    volatile 修饰的变量, 能够保证 “内存可⻅性”.
    比特就业课
    代码在写⼊ volatile 修饰的变量的时候,
    • 改变线程⼯作内存中volatile变量副本的值
    • 将改变后的副本的值从⼯作内存刷新到主内存
    代码在读取 volatile 修饰的变量的时候,
    • 从主内存中读取volatile变量的最新值到线程的⼯作内存中
    • 从⼯作内存中读取volatile变量的副本
public class demo5 {
    // 创建两个线程
    // * 1. 第一个线程,不停循环去执行自己的任务
    // * 2. 第二个线程,输入一个停止标识,使第一个线程退出
    static int flag = 0;
    static Object joker;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1线程启动");
            while (flag == 0) {
                //不停循环,处理任务
            }
            System.out.println("t1线程结束");
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            System.out.println("t2线程启动");
            Scanner scanner=new Scanner(System.in);
            System.out.println("输入一个非零整数");
            flag=scanner.nextInt();
            System.out.println("t2线程结束");
        });
        t2.start();
    }
    }

在这里插入图片描述
在这里插入图片描述
使用volatile
在这里插入图片描述
volatile特性:
1.不保证原子性
2.实现了内存可见性
3.禁止指令重排序

4.wait()方法和notify()方法

wait()⽅法
wait 做的事情:
• 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
• 释放当前的锁
• 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.
wait 要搭配 synchronized 来使⽤. 脱离synchronized 使⽤ wait 会直接抛出异常.
wait 结束等待的条件:
• 其他线程调⽤该对象的 notify ⽅法.
• wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
• 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.

notify ⽅法是唤醒等待的线程.
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其
它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
完,也就是退出同步代码块之后才会释放对象锁

public static void main(String[] args) {
        // 定义一个锁对象
        Object locker = new Object();
        
        // 创建调用wait() 的线程
        Thread t1 = new Thread(() -> {
            while (true) {


                synchronized (locker) {
                    System.out.println("调用wait()之前...");
                    // 执行线程的逻辑
                    // 如果没有满足线程所需要的数据,那么就等待
                    try {
                        locker.wait(0l);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("wait()唤醒之后..");
                    System.out.println("===================");
                }

            }
        });

        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("notifyAll()之前...");
                // 同等待的锁对象进行唤醒
                synchronized (locker) {
                    locker.notifyAll();
               }
                System.out.println("notifyAll()之后...");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动线程
        t1.start();
        t2.start();
    }

在这里插入图片描述
wait()与notify()必须配合synchronized使用,并且使用同一对象
在这里插入图片描述

相关文章:

  • 蓝桥杯真题0团建dfs+哈希表/邻接表
  • 统计登录系统10秒内连续登录失败超过3次的用户
  • 看 MySQL InnoDB 和 BoltDB 如何写磁盘
  • Vivado IP核之定点数累加Accumulator使用说明
  • vscode接入DeepSeek 免费送2000 万 Tokens 解决DeepSeek无法充值问题
  • 向量数据库的选择与应用:AI工程实践
  • Android Retrofit 框架注解定义与解析模块深度剖析(一)
  • HarmonyOS NEXT开发实战:DevEco AI辅助编程工具(CodeGenie)的使用
  • requests中post中data=None, json=None两个参数区别
  • Git 的详细介绍及用法
  • JESD204B协议及IP仿真
  • 什么是进程线程
  • ubuntu 和 RV1126 交叉编译Mosqutiio-1.6.9
  • linux批量使用多个用户名登录脚本、为了给主机增加一个指定用户名的登录记录、无需root密码的主机切换到root方式
  • 【玩转全栈】---- Pinia 组件状态管理器
  • 卷积神经网络与计算机视觉:从数学基础到实战应用
  • 沉浸式CSS学习路径
  • 【贪心算法3】
  • git设置本地仓库和远程仓库
  • 解决 word 2016 粘贴图片老是乱飘的问题
  • 平湖网站建设/网络运营怎么学
  • 做门户型网站/每日精选12条新闻
  • 有网站怎么做seo推广/百度入口网站
  • 如何做b2b网站信息/网络测试
  • 德育工作网站建设方案/百度指数的功能
  • 石家庄电子商务网站建设/宁德市区哪里好玩