【Java多线程编程】解决线程的不安全问题之synchronized关键字

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第1张图片

前言: 

当我们进行多线程编程时候,多个线程抢占系统资源就会造成程序运行后达不到想要的需求。我们可以通过 synchronized 关键字对某个代码块或操作进行加锁。这样就能达到多个线程安全的执行,因此我把如何使用 synchronized 进行加锁的操作过程分享给大家。

目录

1. 线程的不安全原因

1.1 原子性

1.2 解决线程不安全问题

2. synchronized关键字

2.1 synchronized的参数

2.2 synchronized的范围


1. 线程的不安全原因


1.1 原子性

何为原子性,就是唯一的不可分割的

举个例子,有一厕所,三个线程想要上这个厕所。但是这个厕所只有一个,线程1进去了那么线程2和线程3就不得进入这个厕所了。

如果线程1进入厕所后,线程2和线程3强行进入厕所使得线程1不能好好上厕所这样就没有隐私可言,相对来说也是没有原子性。

因此,我们可以把厕所上个锁,使得厕所只能进入一个人,这样也就具备了原子性。

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第2张图片


一条 Java 语句并不总是原子性的,如一个 count++ 语句。它在 cpu 中存在三个步骤:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

上述 Java 语句。如果一个线程正在对进行 count++ 操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的,也是不具备原子性。

但这个条 Java 语句通过上锁的形式,就能达到原子性。如何上锁在下方有讲解。


案例:

当多个线程运行时,就会导致线程的不安全。其原因在于各个线程会抢占资源。比如以下代码:

//创建一个自定义类
class myThread {
    int count = 0;
    public void run() {
        count++;
    }
    public int getCount() {
        return count;
    }
}

public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//实例化这个类
        
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });

        thread1.start();//启动线程thread1
        thread2.start();//启动线程thread2

        thread1.join();//等待线程thread1结束
        thread2.join();//等待线程thread2结束

        System.out.println(myThread.getCount());//获取count值
    }
}

运行后输出:

以上代码的需求为:两个线程分别计算10000 次,使得 count 总数达到 20000。但实际运行后输出的值一个小于 20000 的数字,也可以说输出的值是一个小于 20000 的随机值。因此,我们可以认为这就是线程的不安全。

在以上代码中 count++ 这个操作它是由 cpu 的三个指令构成的:

  • load,把内存中的数据读取到 cpu 寄存器中。
  • add,把寄存器中的值进行++运算
  • save,把寄存器中的值写回到内存中

因此,当线程 thread1 在进行 count++ 操作时,thread2 也进行 count++ 操作。这样就会导致thread1 线程未执行完毕就执行 thread2 线程了,这样的两个线程就是不安全的线程。

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第3张图片

上图中 thread1线程 进行 count++ 操作, load 指令把 1 加载到内存中。

thread2 线程未等thread1 线程 add、save 操作执行完毕,则进行了 count++ 操作。

因此 thread1 中的 load指令 被threa2 中的 load指令 覆盖了。

原本 count++ 操作需要执行两次,thread2 线程抢占 thread1 线程导致 count++ 操作相当于只执行一次。


1.2 解决线程不安全问题

上述1.1中的几个例子,我们了解到多线程操作时候,线程之前会抢占资源造成线程不安全问题。

因此,我们可以通过加锁的形式使线程不得随意抢占资源。加锁是通过 synchronized 关键字进行加锁,请看下方讲解。


2. synchronized关键字

synchronized 关键字就是给线程加锁,当给线程进行加锁后,这个线程就能安全的运行。把上文中的线程进行加锁:

//创建一个自定义类
class myThread {
    int count = 0;
    public void run() {
        synchronized (this){
            count++;
        }
    }
    public int getCount() {
        return count;
    }
}
public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//实例化这个类

        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        thread1.start();//启动线程thread1
        thread2.start();//启动线程thread2
        thread1.join();//等待线程thread1结束
        thread2.join();//等待线程thread2结束
        System.out.println(myThread.getCount());//获取count值
    }
}

运行后输出:

以上代码输出20000,达到了上文中代码的需求。


2.1 synchronized的参数

synchronized 括号里面参数为当前线程的引用,也就是什么引用调用了 synchronized 关键。那么 synchronized 就为这个引用(线程)里面的代码块或操作进行加锁。

        synchronized (this) {
            count++;
        }

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第4张图片对应上图进行理解。

注意,当多个线程对一个对象进行加锁时,此时就会出现“锁竞争”。也就是一个线程拿到了锁,其他线程处于等待(阻塞)状态。类似于本文中的代码。

当多个线程对多个对象进行加锁时,就不会出现“锁竞争”也就是抢占资源的线性,各自加锁各自的线程即可。例如多个线程对应多个厕所,这样就不会出现阻塞:

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第5张图片


2.2 synchronized的范围

synchronized 关键字触发锁是从代码块 {} 开始,出了 {} 锁结束。

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第6张图片

注意,这个线程的里面的锁失效后,其他线程也会抢占这个锁。哪个线程先抢占到这个锁,就先进行锁的约束范围。其他未抢占到锁的线程也是处于等待(阻塞)状态。

举个例子,一个厕所,三个线程想要上这个厕所。当线程1上完厕所后,线程2和线程3会抢占这个厕所。因此,锁也像资源一样会被抢占。

【Java多线程编程】解决线程的不安全问题之synchronized关键字_第7张图片


综上所述,我们可以通过 synchronized 关键字来对线程中的某个代码块进行加锁。这样就能保证程序正在正常的运行,也就解决了线程的不安全问题。当然,关于线程不安全的处理还有另一种方式那就是使用 volatile关键字 ,大家可以在下方专栏中查阅。 

‍作者:程序猿爱打拳,Java领域新星创作者,阿里云社区优质创造者。

️文章收录于:Java多线程编程

️JavaSE的学习:JavaSE

️Java数据结构:数据结构与算法

 本篇文章到这里就结束了,感谢点赞、评论、收藏、关注~

你可能感兴趣的:(Java多线程编程,java,jvm,intellij-idea,javaee,多线程)