并发编程(高并发、多线程)

news/2024/6/18 4:26:39 标签: java, jvm, oracle

并发编程

  • 1.并发编程
    • 1.1 Java程序中怎么保证多线程的运行安全?(难度:★★ 频率:★★★★★)
      • 1.1.1 并发编程三要素
      • 1.1.2 原子性、可见性、有序性问题的解决方法
    • 1.2 Synchronized(难度:★★ 频率:★★★)
      • 1.2.1 synchronized的三种加锁方法
      • 1.2.2 提高synchronized的并发性能
    • 1.3 volatile(难度:★★ 频率:★★★★)

1.并发编程

1.1 Java程序中怎么保证多线程的运行安全?(难度:★★ 频率:★★★★★)

1.1.1 并发编程三要素

首先我们要了解并发编程的三要素

  • 原子性
  • 可见性
  • 有序性

1.原子性
原子性是指一个操作是不可分割的单元,要么全部执行成功,要么全部失败。

在并发环境中,多个线程可能同时访问和修改共享的数据,为了确保数据的一致性,需要确保一组相关的操作是原子执行的。

例如,如果多个线程同时尝试更新同一个变量,需要使用锁或其他同步机制来确保原子性。

2.可见性
可见性是指一个线程对共享数据的修改应该对其他线程是可见的

在多处理器系统中,每个线程可能在不同的处理器上执行,它们有各自的缓存。因此,对一个线程的修改可能不会立即被其他线程看到。为了确保可见性,需要使用同步机制,例如锁或volatile变量,来通知其他线程共享数据的变化。

3.有序性
有序性是指程序的执行应该按照一定的顺序来进行,而不是随机的。

在多线程环境中,由于指令重排等原因,线程执行的顺序可能与程序中编写的顺序不同。为了确保有序性,需要使用同步机制来保持程序的预期执行顺序。

1.1.2 原子性、可见性、有序性问题的解决方法

1.线程切换带来的原子性问题解决办法:

  • 同步机制
    使用synchronized关键字ReentrantLock锁确保一段代码在同一时刻只能被一个线程执行
  • 原子类
    使用AtomicIntegerAtomicLong等, 原子类底层是通过CAS操作来保证原子性的
  • 事务(数据库操作)
    如果涉及到数据库操作,可以使用数据库事务来确保一系列操作的原子性。数据库事务通常在开始和结束时设置边界,确保整个操作在一个原子性的单元中执行。
  • 乐观锁机制
    在执行更新操作时检查数据是否被其他线程修改。如果数据未被修改, 允许更新; 否则执行冲突解决策略。这样可以避免线程切换导致的原子性问题。(存在ABA问题)
java">public class Example {
    private AtomicReference<Data> dataReference = new AtomicReference<>();

    public void updateData() {
        Data currentData = dataReference.get();
        // 在更新前检查数据是否被其他线程修改
        // ...
        // 更新数据
        dataReference.compareAndSet(currentData, newData);
    }
}

2.缓存导致的可见性问题解决办法

  • 使用volatile关键字
    使用volatile关键字来修饰共享的变量。volatile保证了变量的可见性,即当一个线程修改了volatile变量的值,这个变化对其他线程是立即可见的
  • 使用synchronized关键字
    使用synchronized关键字来保护对共享数据的访问, 确保在进入和退出同步块时, 会刷新缓存, 从而保证可见性。
  • 使用Lock接口
    显式地使用Lock接口及其实现类, 如ReentrantLock, 以及ReadWriteLock可以提供更灵活的同步控制,确保在锁的释放时将数据的变化同步到主内存。
  • 使用JUC(并发工具)
    java.util.concurrent包提供了一些用于并发编程的工具,例如Atomic类、CountDownLatch、CyclicBarrier等。这些工具通常会处理可见性问题,避免了手动进行同步的复杂性。

3.编译优化带来的有序性问题解决办法

  • 使用volatile关键字
    volatile关键字不仅保证了变量的可见性,还防止了编译器对被volatile修饰的变量进行重排序。volatile变量的读写都会插入内存屏障,防止指令重排序。
  • 使用synchronized关键字或锁
    使用synchronized关键字或锁也能够防止指令重排序,因为在进入和退出同步块时都会插入内存屏障,确保了代码块的有序性。
  • 使用final关键字
    在Java中,final关键字除了用于声明常量外,还可以用于修饰字段、方法和类。对于字段,final关键字可以防止字段的写入重排序。
  • 使用happens-before规则
    在Java中,happens-before规则定义了在多线程环境下操作的顺序。通过正确使用同步、volatile等机制,可以利用happens-before规则来确保代码的正确有序执行。
  • 使用JUC(并发工具)
    java.util.concurrent包中的一些工具类,如CountDownLatch、CyclicBarrier等,也可以防止编译器对代码进行过度优化,确保有序性。

1.2 Synchronized(难度:★★ 频率:★★★)

1.2.1 synchronized的三种加锁方法

java">// 修饰普通方法
public synchronized void increase() {
   
}

// 修饰静态方法
public static synchronized void increase() {
   
}

// 修饰代码块
public Object synMethod(Object a1) {
    synchronized(a1) {
        // 操作
    }
}
作为范围锁对象
普通方法当前实例对象(this), 对于Class类的不同实例, 它们的实力方法是独立的, 可以同时执行
静态方法整个类的Class对象, 对于Class类的所有实例,同一时间只能有一个线程执行该静态方法
代码块指定对象

1.2.2 提高synchronized的并发性能

  1. 减小同步块的范围: 尽量缩小使用 synchronized 保护的代码块范围,以减少线程持有锁的时间。只在必要的代码块上使用同步。
  2. 使用局部变量代替共享变量: 尽量使用局部变量而不是共享变量,这可以减小锁的粒度,减少竞争
  3. 使用读写锁: 如果读操作远远多于写操作,可以考虑使用 ReentrantReadWriteLock,以允许多个线程同时读取而不互斥。
  4. 考虑锁分离: 将对象的锁分离,使用不同的锁来保护对象的不同部分,从而减小锁的争用。

扩展问题1: Synchronized修饰的方法在抛出异常时,会释放锁吗?
当一个线程执行一个被 synchronized 关键字修饰的方法时,如果发生异常,虚拟机会将锁释放,允许其他线程进入相同的方法或代码块。这样,其他线程有机会执行相应的同步代码,而不会被阻塞。

java">public class SynchronizedExample {
    private static int counter = 0;

    public synchronized void synchronizedMethod() {
        System.out.println(Thread.currentThread().getName() + " entering synchronizedMethod.");

        if (counter < 3) {
            counter++;
            System.out.println(Thread.currentThread().getName() + " Counter: " + counter);
            throw new RuntimeException("Simulating an exception.");
        }

        System.out.println(Thread.currentThread().getName() + " exiting synchronizedMethod.");
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();

        // 创建两个线程调用同一个对象的同步方法
        Thread thread1 = new Thread(() -> {
            example.synchronizedMethod();
        });

        Thread thread2 = new Thread(() -> {
            example.synchronizedMethod();
        });

        thread1.start();
        thread2.start();
    }
}

扩展问题2: synchronized 是公平锁还是非公平锁?
synchronized关键字默认是非公平锁。这意味着在多个线程竞争同一个锁的情况下,无法保证线程获取锁的顺序与线程请求锁的顺序一致。

非公平锁的特点是,当一个线程释放锁时,下一个要获得锁的线程是任意选择的,不考虑这个线程是否在等待队列中等待更长的时间。

1.3 volatile(难度:★★ 频率:★★★★)

在这里插入图片描述
1.保证可见性
当一个变量被声明为volatile时,对该变量的读写操作都会直接在主内存中进行,而不会在线程的本地缓存中进行。这确保了一个线程对该变量的修改对其他线程是可见的。即使一个线程修改了volatile变量,其他线程立即看到的是最新的值。

java">public class VisibilityExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        // 线程 A
        new Thread(() -> {
            System.out.println("Thread A started");
            while (!flag) {
                // 在没有使用 volatile 的情况下,可能会陷入无限循环,因为 flag 的修改对线程 A 不可见
            }
            System.out.println("Thread A finished");
        }).start();

        try {
            Thread.sleep(1000); // 等待一段时间,确保线程 A 已经启动
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 线程 B
        new Thread(() -> {
            System.out.println("Thread B started");
            flag = true; // 修改 flag 的值
            System.out.println("Thread B set flag to true");
        }).start();
    }
}

2.禁止指令重排序
在 Java 中,编译器和处理器为了提高性能可能会对指令进行重排序。对于一些涉及多线程的代码,这种重排序可能导致意外的结果。通过使用 volatile,可以禁止特定类型的指令重排序,从而确保操作的顺序符合程序员的预期。


http://www.niftyadmin.cn/n/5295401.html

相关文章

快来检测一下你是否真的学会了C语言,保证你看完后收获满满!!

文章目录 每日一言1234567891011121314151617181920结语 每日一言 人生而自由&#xff0c;却无往不在枷锁中。 --社会契约论 1 以下程序段的输出结果是&#xff1f; char s[]"\\141\141abc\t"; printf("%d\n",strlen(s));A. 9 B. 12 C. 13 D. 14 正确答…

计算机基础--Linux详解

你是否曾经想过&#xff0c;为什么你的手机、电脑&#xff0c;甚至是智能电视&#xff0c;都能通过网络互相交流&#xff1f;或者为什么你在网上购买了一款软件&#xff0c;它会自动安装到你的设备上&#xff1f;这些都离不开一个名为Linux的操作系统。今天&#xff0c;让我们一…

条款 12:拷贝对象的所有部分

编译器生成的拷贝函数&#xff08;拷贝构造函数&#xff0c;拷贝赋值运算符&#xff09;&#xff0c;会拷贝对象的所有数据&#xff0c;当你声明自己的拷贝函数时&#xff0c;就是在告诉编译器&#xff0c;默认实现中有你不喜欢的地方。 void logCall(const std::string& …

冠赢互娱基于 OpenKrusieGame 实现游戏云原生架构升级

作者&#xff1a;力铭 关于冠赢互娱 冠赢互娱是一家集手游、网游、VR 游戏等研发、发行于一体的游戏公司&#xff0c;旗下官方正版授权的传奇类手游——《仙境传奇》系列深受广大玩家们的喜爱。基于多年 MMORPG 类型游戏的自研与运营经验&#xff0c;冠赢互娱正式推出了 2D M…

OpenFeign相关面试题及答案

1、什么是OpenFeign&#xff0c;它如何简化远程服务调用&#xff1f; OpenFeign是一个声明式的Web服务客户端&#xff0c;它使得编写HTTP客户端变得更加容易。它属于Spring Cloud Netflix项目的一部分&#xff0c;可以与Spring Boot应用轻松集成。通过使用OpenFeign&#xff0…

基于Java SSM框架实现实现中国古诗词学习平台项目【项目源码】计算机毕业设计

基于java的SSM框架实现中国古诗词学习平台系统演示 JSP技术介绍 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了用…

Loading 加载 Taro + vue3 自定义组件的封装和 分页 优化

1.需求 当需要实现一个组件 上拉加载的组件 我们可以选择某些组件库的组件。 但是有的组件没有这个组件&#xff0c;比如跟Taro 框架配套的京东nut-ui组件库 没有提供这个功能, 2.Loading组件 ①封装 <template><div class"container"><div class&…

SQL注入【ByPass有点难的靶场实战】(九)

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 0、总体思路 先确认是否可以SQL注入&#xff0c;使用单…