Rumah  >  Artikel  >  Java  >  Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

WBOY
WBOYke hadapan
2022-04-21 18:17:511964semak imbas

Artikel ini membawa anda pengetahuan yang berkaitan tentang java, yang terutamanya memperkenalkan isu yang berkaitan dengan berbilang benang, termasuk pemasangan benang, mengunci benang dan sebab tidak selamat benang, kategori standard Keselamatan benang dan kandungan lain, saya harap ia akan membantu semua orang.

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

Kajian yang disyorkan: "tutorial video java"

Artikel ini memperkenalkan isu keselamatan benang dalam Java multi-threading Isu keselamatan di sini jangan merujuk kepada isu keselamatan yang disebabkan oleh penggodam Isu keselamatan benang merujuk kepada pepijat dalam program yang disebabkan oleh pelaksanaan preemptive berbilang benang.

1. Gambaran keseluruhan keselamatan benang

1.1 Apakah isu keselamatan benang

Pertama sekali, kita perlu memahami bahawa penjadualan benang dalam sistem pengendalian adalah preemptive, atau rawak . , yang menyebabkan susunan pelaksanaan utas menjadi tidak pasti apabila penjadualan utas dilaksanakan Beberapa kod mempunyai perintah pelaksanaan yang berbeza dan tidak akan menjejaskan hasil menjalankan program Walau bagaimanapun, terdapat juga beberapa kod yang mengubah susunan pelaksanaan dan hasil jalan yang ditulis semula akan terjejas. Ini adalah Kod yang menyebabkan pepijat dalam program apabila berbilang utas serentak dipanggil kod tidak selamat utas Ini adalah isu keselamatan utas.

Di bawah, kami akan memperkenalkan contoh biasa masalah keselamatan benang, masalah kenaikan diri integer.

1.2 Program dengan isu keselamatan benang

Pada suatu hari, guru memberikan masalah sedemikian: gunakan dua utas untuk menambah pembolehubah count sebanyak 10 sepuluh ribu kali, setiap utas Menjalankan 5 sepuluh ribu kali ganda tugasan kenaikan automatik, dan nilai awal pembolehubah count ialah 0.
Soalan ini sangat mudah. ​​Kami juga boleh mengira keputusan akhir secara lisan. 10 Xiao Ming bekerja dengan cepat dan pantas menulis sekeping kod berikut:

class Counter {
    private int count;
    public void increase() {
        ++this.count;
    }
    public int getCount() {
        return this.count;
    }}public class Main11 {
    private static final int CNT = 50000;
    private static final Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i  {
            for (int j = 0; j  Secara logiknya, hasilnya sepatutnya <p> juta. Mari kita lihat hasil larian: <code>10</code> <br><img src="https://img.php.cn/upload/article/000/000/067/5a3746e6458021f78f91292b5a885487-1.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"> Hasil larian adalah lebih kecil daripada <br> juta Anda boleh cuba menjalankan program dan anda akan mendapati bahawa hasilnya berbeza setiap kali, tetapi dalam kebanyakan kes, hasilnya akan lebih kecil daripada nilai yang dijangkakan. . Xiao, mari analisa mengapa ini berlaku. <code>10</code></p><h2>2. Sebab penguncian benang dan benang tidak selamat<strong></strong>
</h2>2.1 Analisis Kes<h2></h2>Di atas kami menggunakan multi-threading untuk menjalankan program dan meletakkan The pembolehubah dengan nilai pembolehubah 0 dinaikkan sebanyak 100,000 kali, tetapi hasil sebenar akhir adalah lebih kecil daripada hasil jangkaan kami. Sebabnya ialah susunan penjadualan benang adalah rawak, menyebabkan set arahan kenaikan diri bersilang antara benang, mengakibatkan. dua peningkatan sendiri semasa masa jalan meningkat tetapi nilai hanya meningkat sekali, jadi hasil yang diperoleh akan menjadi lebih kecil. <p></p>Kami tahu bahawa operasi kenaikan boleh termasuk arahan berikut: <p></p><ol>Muatkan nilai pembolehubah dalam memori ke dalam daftar Anda mungkin ingin merekodkan operasi ini sebagai <li>. <code>load</code>
</li> melakukan operasi kenaikan dalam daftar Anda mungkin ingin merekodkan operasi ini sebagai <li>. <code>add</code>
</li> Simpan nilai daftar ke memori Anda mungkin ingin merekodkan operasi ini sebagai <li>. <code>save</code>
</li>
</ol>Mari lukis garis masa untuk meringkaskan beberapa situasi biasa: <p></p><p>Kes 1: <strong> Set arahan antara benang, tiada persimpangan, hasil berjalan Seperti yang dijangka, daftar A dalam rajah mewakili daftar yang digunakan oleh benang 1, dan daftar B mewakili daftar yang digunakan oleh benang 2. Perkara yang sama berlaku untuk situasi berikutnya. </strong><br><img src="https://img.php.cn/upload/article/000/000/067/1a7f0bb1b7c2552a80983e04b900dc15-2.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br>Kes 2: <strong> Terdapat pertindihan dalam set arahan antara utas dan hasil yang dijalankan adalah lebih rendah daripada yang dijangkakan. </strong><br><img src="https://img.php.cn/upload/article/000/000/067/1a7f0bb1b7c2552a80983e04b900dc15-3.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br>Kes 3: <strong> Selesaikan silang set arahan antara urutan, keputusan sebenar adalah lebih rendah daripada jangkaan. </strong><br><img src="https://img.php.cn/upload/article/000/000/067/4e482e48ea6191128516ca82417af3cf-4.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"> Mengikut situasi yang kami senaraikan di atas, didapati hasil larian adalah normal apabila tiada arahan silang semasa benang sedang berjalan, tetapi apabila terdapat salib, keputusan auto -operasi increment akan menjadi kurang<br>Ringkasnya, satu kesimpulan boleh dibuat, iaitu, kerana operasi auto-increment bukan atom, pelaksanaan serentak berbilang benang berkemungkinan menyebabkan pelaksanaan arahan bertindih, yang membawa kepada isu keselamatan benang. <code>1</code></p>Jadi bagaimana untuk menyelesaikan masalah tidak selamat di atas? Sudah tentu ada, iaitu untuk mengunci objek. <p></p>2.2 Penguncian benang<h2></h2><h3>2.2.1 Apakah yang dikunci<strong></strong>
</h3>Untuk menyelesaikan masalah keselamatan benang yang disebabkan oleh "pelaksanaan preemptive", Kita boleh mengunci objek operasi. Apabila benang mendapat kunci objek, ia akan mengunci objek. Jika benang lain perlu melaksanakan tugas objek, mereka perlu menunggu sehingga benang selesai menjalankan tugas objek. boleh dilaksanakan. <p></p><p>举个例子,假设要你去银行的ATM机存钱或者取款,每台ATM机一般都在一间单独的小房子里面,这个小房子有一扇门一把锁,你进去使用ATM机时,门会自动的锁上,这个时候如果有人要来取款,那它得等你使用完并出来它才能进去使用ATM,那么这里的“你”相当于线程,ATM相当于一个对象,小房子相当于一把锁,其他的人相当于其他的线程。<br><img src="https://img.php.cn/upload/article/000/000/067/4e482e48ea6191128516ca82417af3cf-5.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br><img src="https://img.php.cn/upload/article/000/000/067/6e6c80338fcb268f141a907ecdcadb51-6.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br> 在java中最常用的Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java操作就是使用<code>synchronized</code>关键字进行Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java。</p><h3>2.2.2如何Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java</h3><p>synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。<br> 线程进入 synchronized 修饰的代码块, 相当于<code>Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java</code>,退出 synchronized 修饰的代码块, 相当于 <code>解锁</code>。</p><p>java中的Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java操作可以使用<code>synchronized</code>关键字来实现,它的常见使用方式如下:</p><p><strong>方式1:</strong> 使用<code>synchronized</code>关键字修饰普通方法,这样会使方法所在的对象加上一把锁。<br> 例如,就以上面自增的程序为例,尝试使用<code>synchronized</code>关键字进行Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,如下我对<code>increase</code>方法进行了Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,实际上是对某个对象Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,此锁的对象就是<code>this</code>,本质上Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java操作就是修改<code>this</code>对象头的标记位。</p><pre class="brush:php;toolbar:false">class Counter {
    private int count;
    synchronized public void increase() {
        ++this.count;
    }
    public int getCount() {
        return this.count;
    }}

多线程自增的main方法如下,后面会以相同的栗子介绍synchronized的其他用法,后面就不在列出这段代码了。

public class Main11 {
    private static final int CNT = 50000;
    private static final Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i  {
            for (int j = 0; j <p>看看Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:<br><img src="https://img.php.cn/upload/article/000/000/067/6e6c80338fcb268f141a907ecdcadb51-7.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br><strong>方式2:</strong> 使用<code>synchronized</code>关键字对代码段进行Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,但是需要显式指定Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java的对象。<br> 例如:</p><pre class="brush:php;toolbar:false">class Counter {
    private int count;
    public void increase() {
        synchronized (this){
            ++this.count;
        }
    }
    public int getCount() {
        return this.count;
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java
方式3: 使用synchronized关键字修饰静态方法,相当于对当前类的类对象进行Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java。

class Counter {
    private static int count;
    synchronized public static void increase() {
        ++count;
    }
    public int getCount() {
        return this.count;
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java
常见的用法差不多就是这些,对于线程Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java(线程拿锁),如果两个线程同时拿一个对象的锁,就会产生锁竞争,两个线程同时拿两个不同对象的锁不会产生锁竞争。
对于synchronized这个关键字,它的英文意思是同步,但是同步在计算机中是存在多种意思的,比如在多线程中,这里同步的意思是“互斥”;而在IO或网络编程中同步指的是“异步”,与多线程没有半点的关系。

synchronized 的工作过程:

  1. 获得互斥锁lock
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁unlock

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题,即死锁问题,关于死锁后续文章再做介绍。

综上,synchronized关键字Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java有如下性质:互斥性,刷新内存性,可重入性。

synchronized关键字也相当于一把监视器锁monitor lock,如果不Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,直接使用wait方法(一种线程等待的方法,后面细说),会抛出Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,引发这个异常的原因就是没有Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java。

2.2.3再析案例

对自增那个代码上锁后,我们再来分析一下为什么加上了所就线程安全了,先列代码:

class Counter {
    private int count;
    synchronized public void increase() {
        ++this.count;
    }
    public int getCount() {
        return this.count;
    }}public class Main11 {
    private static final int CNT = 50000;
    private static final Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i  {
            for (int j = 0; j <p>多线程并发执行时,上一次就分析过没有指令集交叉就不会出现问题,因此这里我们只讨论指令交叉后,Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java操作是如何保证线程安全的,不妨记Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java为<code>lock</code>,解锁为<code>unlock</code>,两个线程运行过程如下:<br> 线程1首先拿到目标对象的锁,对对象进行Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,处于<code>lock</code>状态,当线程2来执行自增操作时会发生阻塞,直到线程1的自增操作完毕,处于<code>unlock</code>状态,线程2才会就绪取执行线程2的自增操作。<br><img src="https://img.php.cn/upload/article/000/000/067/aacb0e52e264f65d74e598d37852834f-10.png" alt="Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java"><br> Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java后线程就是串行执行,与单线程其实没有很大的区别,那多线程是不是没有用了呢?但是对方法Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java后,线程运行该方法才会Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,运行完该方法就会自动解锁,况且大部分操作并发执行是不会造成线程安全的,只有少部分的修改操作才会有可能导致线程安全问题,因此整体上多线程运行效率还是比单线程高得多。</p><h2>2.3线程不安全的原因</h2><p>首先,线程不安全根源是线程间的调度充满随机性,导致原有的逻辑被改变,造成线程不安全,这个问题无法解决,无可奈何。</p><p>多个线程针对同一资源进行写(修改)操作,并且针对资源的修改操作不是原子性的,可能会导致线程不安全问题,类似于数据库的事务。</p><p>由于编译器的优化,内存可见性无法保证,就是当线程频繁地对同一个变量进行读操作时,会直接从寄存器上读值,不会从内存上读值,这样内存的值修改时,线程就感知不到该变量已经修改,会导致线程安全问题(这是编译器优化的结果,现代的编译器都有类似的优化不止于Java),因为相比于寄存器,从内容中读取数据的效率要小的多,所以编译器会尽可能地在逻辑不变的情况下对代码进行优化,单线程情况下是不会翻车的,但是多线程就不一定了,比如下面一段代码:</p><pre class="brush:php;toolbar:false">import java.util.Scanner;public class Main12 {
    private static int isQuit;
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (isQuit == 0) {

            }
            System.out.println("线程thread执行完毕!");
        });
        thread.start();

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入isQuit的值,不为0线程thread停止执行!");
        isQuit = sc.nextInt();
        System.out.println("main线程执行完毕!");
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java
我们从Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java可以知道,输入isQuit后,线程thread没有停止,这就是编译器优化导致线程感知不到内存可见性,从而导致线程不安全。
我们可以使用Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java关键字保证内存可见性。
我们可以使用Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java关键字修饰isQuit来保证内存可见性。

import java.util.Scanner;public class Main12 {
    Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java private static int isQuit;
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (isQuit == 0) {

            }
            System.out.println("线程thread执行完毕!");
        });
        thread.start();

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入isQuit的值,不为0线程thread停止执行!");
        isQuit = sc.nextInt();
        System.out.println("main线程执行完毕!");
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

synchronized与Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java关键字的区别:
synchronized关键字能保证原子性,但是是否能够保证内存可见性要看情况(上面这个栗子是不行的),而Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java关键字只能保证内存可见性不能保证原子性。
保证内存可见性就是禁止编译器做出如上的优化而已。

import java.util.Scanner;public class Main12 {
    private static int isQuit;
    //锁对象
    private static final Object lock = new Object();
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
                synchronized (lock) {
                    while (isQuit == 0) {

                    }
                    System.out.println("线程thread执行完毕!");
                }
        });
        thread.start();

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入isQuit的值,不为0线程thread停止执行!");
        isQuit = sc.nextInt();
        System.out.println("main线程执行完毕!");
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

编译器优化除了导致内存可见性感知不到的问题,还有指令重排序也会导致线程安全问题,指令重排序也是编译器优化之一,就是编译器会智能地(保证原有逻辑不变的情况下)调整代码执行顺序,从而提高程序运行的效率,单线程没问题,但是多线程可能会翻车,这个原因了解即可。

3.线程安全的标准类

Java 标准库中很多都是线程不安全的。这些类可能会涉及到多线程修改共享数据, 又没有任何Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java措施。例如,ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder。
但是还有一些是线程安全的,使用了一些锁机制来控制,例如,Vector (不推荐使用),HashTable (不推荐使用),ConcurrentHashMap (推荐),StringBuffer。
还有的虽然没有Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java, 但是不涉及 “修改”, 仍然是线程安全的,例如String。

在线程安全问题中可能你还会遇到JMM模型,在这里补充一下,JMM其实就是把操作系统中的寄存器,缓存和内存重新封装了一下,其中在JMM中寄存器和缓存称为工作内存,内存称为主内存。
其中缓存分为一级缓存L1,二级缓存L2和三级缓存L3,从L1到L3空间越来越大,最大也比内存空间小,最小也比寄存器空间大,访问速度越来越慢,最慢也比内存的访问速度快,最快也没有寄存器访问快。

4.Object类提供的线程等待方法

除了Thread类中的能够实现线程等待的方法,如join,sleep,在Object类中也提供了相关线程等待的方法。

序号 方法 说明
1 public final void wait() throws InterruptedException 释放锁并使线程进入WAITING状态
2 public final native void wait(long timeout) throws InterruptedException; 相比于方法1,多了一个最长等待时间
3 public final void wait(long timeout, int nanos) throws InterruptedException 相比于方法2,等待的最长时间精度更大
4 public final native void notify(); 唤醒一个WAITING状态的线程,并Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,搭配wait方法使用
5 public final native void notifyAll(); 唤醒所有处于WAITING状态的线程,并Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java(很可能产生锁竞争),搭配wait方法使用

上面介绍synchronized关键字的时候,如果不对线程Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java会产生非法监视异常,我们来验证一下:

public class TestDemo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("执行完毕!");
        });

        thread.start();
        System.out.println("wait前");
        thread.wait();
        System.out.println("wait后");
    }}

看看Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java
果然抛出了一个IllegalMonitorStateException,因为wait方法的执行步骤为:先释放锁,再使线程等待,你现在都没有Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java,那如何释放锁呢?所以会抛出这个异常,但是执行notify是无害的。

wait方法常常搭配notify方法搭配一起使用,前者能够释放锁,使线程等待,后者能获取锁,使线程继续执行,这套组合拳的流程图如下:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

现在有两个任务由两个线程执行,假设线程2比线程1先执行,请写出一个多线程程序使任务1在任务2前面完成,其中线程1执行任务1,线程2执行任务2。
这个需求可以使用wait/notify来实现。

class Task{
    public void task(int i) {
        System.out.println("任务" + i + "完成!");
    }}public class WiteNotify {
    //锁对象
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                Task task1 = new Task();
                task1.task(1);
                //通知线程2线程1的任务完成
                System.out.println("notify前");
                lock.notify();
                System.out.println("notify后");
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                Task task2 = new Task();
                //等待线程1的任务1执行完毕
                System.out.println("wait前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                task2.task(2);
                System.out.println("wait后");
            }
        });
        thread2.start();
        Thread.sleep(10);
        thread1.start();
    }}

Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java:
Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java

推荐学习:《java视频教程

Atas ialah kandungan terperinci Mari kita bincangkan tentang isu keselamatan benang berbilang benang Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:csdn.net. Jika ada pelanggaran, sila hubungi admin@php.cn Padam