掌握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()方法时,它会自动获取与该方法的对象关联的内部锁,确保没有其他线程可以并发修改计数器。一旦方法完成执行,锁就会被释放,从而允许其他线程调用该方法。

相关推荐
2401_857439692 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT3 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
一 乐4 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
艾伦~耶格尔7 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20177 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
攸攸太上8 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
罗曼蒂克在消亡8 小时前
graphql--快速了解graphql特点
后端·graphql
潘多编程8 小时前
Spring Boot与GraphQL:现代化API设计
spring boot·后端·graphql
大神薯条老师9 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析
2401_857622669 小时前
Spring Boot新闻推荐系统:性能优化策略
java·spring boot·后端