解决并发问题的方法有哪些

盘点下场景并发问题的解决方案:

 图从上图我们看到,解决并发问题的方法分为两大类: 无锁和有锁。无锁可分为:局部变量、不可变对象、ThreadLocal、CAS原子类;而有锁的方式又分为synchronized关键字和ReentrantLock可重入锁。

一、局部变量

        局部变量是在方法、构造函数或代码块内部声明的变量,其作用域仅限于声明它的方法、构造函数或代码块内部。局部变量在每个线程中都有自己的副本,每个线程在访问局部变量时都操作自己的副本,不会对其他线程产生影响。

public class LocalVariableExample {
    public static void main(String[] args) {
        int i = 10; // i是方法内的局部变量
        System.out.println(i); // 输出:10

        // 这里不能直接访问i,因为i的作用域仅限于main方法
    }
}

 二、不可变对象

        不可变对象是指在创建后其状态不能被修改或改变的对象。一旦创建了不可变对象,它的内部状态将保持不变,任何修改操作都不会改变该对象的状态。不可变对象是线程安全的,因为它们的状态不会被多个线程同时修改,从而避免了线程安全问题。

下面是一个示例演示了如何创建一个简单的不可变对象:

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

 在上述示例中,ImmutablePerson类的字段nameage都是私有的和final的,并且没有提供任何修改其状态的方法,因此该类是不可变的。创建一个不可变对象后,其状态将保持不变,其他线程可以安全地访问这个对象。

三、ThreadLocal

ThreadLocal,每个线程都可以独立地存储和获取自己的数据副本,而不会影响其他线程。这样可以避免多线程之间共享数据时可能导致的并发问题,并提供一种简单的线程封闭(Thread Confinement)方法。

ThreadLocal的主要特点:

  1. 线程本地存储ThreadLocal允许每个线程都拥有自己的局部变量副本,线程之间的数据互不干扰。

  2. 数据隔离:通过ThreadLocal,可以实现将数据与线程关联,实现线程隔离。

  3. 简化多线程编程:使用ThreadLocal可以简化多线程编程,无需显式进行线程同步。

  4. 无需额外的锁ThreadLocal避免了使用锁的开销,提高了并发性能。

  5. 对共享数据的访问:每个线程通过ThreadLocal直接访问自己的局部变量副本,无需竞争共享数据。

四、CAS原子类

AS(Compare-And-Swap)是一种并发编程中常用的无锁算法,用于实现原子操作。在多线程环境下,CAS可以解决共享变量的并发问题,保证操作的原子性,避免使用锁带来的性能开销。Java中提供了一系列CAS原子类,位于java.util.concurrent.atomic包下,常用的CAS原子类包括:

  1. AtomicBoolean:提供了原子的boolean值操作,例如get()set()compareAndSet()等。

  2. AtomicInteger:提供了原子的整数操作,例如get()set()getAndIncrement()compareAndSet()等。

  3. AtomicLong:提供了原子的长整数操作,功能类似于AtomicInteger

  4. AtomicReference:提供了对对象引用的原子性操作,例如get()set()compareAndSet()等。

  5. AtomicIntegerArray:提供了原子性的整型数组操作,例如get()set()getAndIncrement()等。

  6. AtomicLongArray:提供了原子性的长整型数组操作,功能类似于AtomicIntegerArray

  7. AtomicReferenceArray:提供了原子性的对象引用数组操作,例如get()set()compareAndSet()等。

使用CAS原子类,我们可以在不使用锁的情况下实现线程安全的操作,例如计数器的自增、自减等。CAS操作是基于底层硬件指令的原子性保证,它利用了CPU提供的CAS指令,实现了在单个CPU指令周期内对内存的读取、比较和写入操作,从而保证了操作的原子性。

以下是一个简单的示例演示了AtomicInteger的使用:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private static AtomicInteger accessCount= new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                int value = accessCount.incrementAndGet();
                System.out.println(Thread.currentThread().getName() + ": " + value);
            });
            thread.start();
        }
    }
}

实际运行情况:

 

在上述示例中,我们创建了一个AtomicInteger对象作为计数器,每个线程通过incrementAndGet()方法对计数器进行原子性的自增操作,无需使用锁,从而实现了线程安全的计数功能。输出结果会显示不同线程对计数器进行递增后的值,由于CAS操作的原子性,结果是正确的并且无竞争。

五、synchronized关键字和ReentrantLock可重入锁

Synchronized和ReentrantLock都是采用了悲观锁的策略。因为他们的实现非常类似,只不过一种是通过语言层面来实现(Svnchronized),另一种是通过编程方式实现(ReentrantLock),所以咱们把两种方式放在一起分析了.

先看看下面例子:

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

public class ReentrantLockExample {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++; // 进行临界区操作
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public int getCount() {
        return count;
    }

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

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.getCount());
    }
}

在上述示例中,我们创建了一个名为ReentrantLockExample的类,它包含一个count整数变量和一个ReentrantLock对象。increment()方法是一个临界区方法,它使用ReentrantLock来获取锁,然后执行对count变量的自增操作,最后释放锁。这样,我们确保在多线程环境下对count的操作是线程安全的。

加锁原理图:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值