掌握Java并发

了解并发

并发是指计算机可以同时处理多个任务。它涉及管理多个任务的执行,以提高软件应用程序的性能和响应能力。

🧵什么是线程

线程就像程序中的一个单独的执行路径。当程序运行时,它可以有多个线程并发运行,每个线程独立地执行自己的指令集。

💾线程缓存的问题

在下面的代码中,我们将Clock类创建为Runnable类,并通过从主类生成新的Thread来运行它。一旦启动,Clock类的run方法将继续运行while循环。为了阻止这种情况,我们需要从主线程调用cancel方法,这将:

  1. isStopped值更改为false。
  2. 把它写在记忆里。

然而,运行while循环的线程可能没有更新的值isStopped,并且可能仍然具有旧值isStopped(false)的本地缓存。

java 复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Clock clock  = new Clock();
        Thread thread = new Thread(clock);
        thread.start();
        Thread.sleep(10000);
        clock.cancel();
    }
}

class Clock implements Runnable {
    private volatile boolean isStopped = false;

    public void cancel() {
        this.isStopped = true;
    }

    public void run() {
        while (!this.isStopped) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Tick");
        }
    }
}

解决方案:Volatile关键字

volatile变量的值永远不会被缓存,所有的写入和读取都将在主存中完成。因此,上面的thread运行while循环将始终具有来自内存的最新读取值。

竞争问题:竞争条件

让我们考虑这样一个场景:我们创建Counter类,并尝试从两个单独的线程更新counter值。

csharp 复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(counter);
        Thread t2 = new Thread(counter);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("expect to be 2000 but value is : " + counter.getCounter());

    }
}


public class Counter implements Runnable{
    private int counter=0;

    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            increament();
        }
    }
    public void increament(){
        int i =counter;
        counter = i+1;
    }
    public int getCounter(){
        return this.counter;
    }
}

你能猜到输出吗?

kotlin 复制代码
expect to be 2000 but value is: 1746

但为什么会这样呢?让我们来理解一下:

看看increment方法:

  1. 首先,我们将计数器的值存储到局部变量i中。
  2. 然后,我们将i递增1。
  3. 最后,我们将i写回counter

两个线程同时执行此操作。

同步解决方案:同步关键字

我们可以使方法同步,这样每次只有一个线程可以运行该方法。其他线程必须等待轮到它们执行该方法。这导致了一致的数据。

我们使用synchronized关键字定义一个要同步的方法。

csharp 复制代码
public class Counter implements Runnable{
    private int counter=0;

    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            increament();
        }
    }
    public synchronized void increament(){
        int i =counter;
        counter = i+1;
    }
    public int getCounter(){
        return this.counter;
    }
}

🧱密码块

如果两个方法处理两个不同的变量,我们可以使用synchronized代码块只锁定每个方法的关键部分,而不是使整个方法同步并锁定整个对象。这种方法允许我们确保没有两个线程可以同时访问同一个方法,从而提供更好的并发性。

java 复制代码
public class ExampleClass {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    private int value1 = 0;
    private int value2 = 0;

    public void incrementValue1() {
        synchronized (lock1) {
            value1++;
            System.out.println("Value 1 incremented to: " + value1);
        }
    }

    public void decrementValue2() {
        synchronized (lock2) {
            value2--;
            System.out.println("Value 2 decremented to: " + value2);
        }
    }
}

例如,假设我们有incrementValue1()decrementValue2()方法,每个方法修改单独的变量value1value2。我们已经声明了两个私有的final对象lock1和lock2。这些对象用作同步块的锁,以确保访问相应变量时的线程安全。通过在每个方法中使用同步代码块,我们可以只锁定修改共享变量的部分,允许并发访问其他方法,同时保持线程安全。

🔐锁定

并发中的锁用于通过启用对共享资源的同步访问来实现线程安全。

- 内在锁

在Java中,每个对象都有一个内置的锁,称为内部锁。当一个线程想要访问一个共享对象时,它必须首先获得这个锁。这确保了一次只有一个线程可以访问对象。当您将一个方法声明为synchronized时,调用该方法的线程将获取该方法对象的内部锁,并在该方法退出时释放该锁

例如,考虑一个Counter类,其中每个线程递增一个共享计数器变量。在这个类中,increment()方法被声明为synchronized,这意味着对于给定的Counter对象,一次只有一个线程可以执行它。当一个线程调用increment()方法时,它会自动获取与该方法的对象关联的内部锁,确保没有其他线程可以并发修改计数器。一旦方法完成执行,锁就会被释放,从而允许其他线程调用该方法。

相关推荐
Cikiss3 分钟前
微服务实战——购物车模块实战
java·开发语言·后端·spring·微服务·springcloud
程序猿进阶4 分钟前
大循环引起CPU负载过高
java·开发语言·后端·性能优化·并发编程·架构设计·问题排查
ss2731 小时前
被催更了,2025元旦源码继续免费送
java·vue.js·spring boot·后端·微信小程序·开源
Lugas1 小时前
使用Vert.x实现反向代理
java·后端
码农君莫笑1 小时前
在 Blazor 和 ASP.NET Core 中使用依赖注入和Scoped 服务实现数据共享方法详解
前端·后端·c#·.netcore·visual studio
庆 、1 小时前
Django REST framework 源码剖析-视图类详解(Views)
后端·python·django·framework·框架·restful·rest
矩阵猫咪1 小时前
creating-custom-commands-in-flask
后端·python·flask
凡人的AI工具箱1 小时前
每天40分玩转Django:Django Celery
数据库·后端·python·django·sqlite
矩阵猫咪1 小时前
create-a-weather-app-using-flask-python
后端·python·flask
JINGWHALE11 小时前
设计模式 结构型 装饰器模式(Decorator Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·装饰器模式