ホームページ  >  記事  >  Java  >  Java のスレッド メモリ モデルに関する詳細なチュートリアル

Java のスレッド メモリ モデルに関する詳細なチュートリアル

零下一度
零下一度オリジナル
2017-05-25 16:14:54986ブラウズ


Javaスレッドメモリモデル

すべてのスレッドはメインメモリを共有し、各スレッドは独自の作業メモリを持っています

refreshing local memory to/from main memory must comply to JMM rules

スレッドセーフの理由

スレッドの作業メモリはレジスタとキャッシュですCPU の概要説明: 今日のコンピューターでは、CPU が計算する際、データ読み取りの優先順位は、レジスター、キャッシュ、メモリー であるとは限りません。スレッドは CPU を消費します。計算プロセス中に、一部のデータが頻繁に読み取られ、これらのキャッシュされたデータはスレッドが計算を完了した後に書き戻される必要があります。必要に応じて思い出してください。複数のスレッドが特定のメモリ データを同時に読み書きすると、原子性、順序性、可視性という 3 つの特性に関わるマルチスレッドの同時実行性の問題が発生します。 マルチスレッドをサポートするプラットフォームはこの問題に直面することになり、マルチスレッド プラットフォーム上で実行されるマルチスレッドをサポートする言語は、この問題に対する解決策を提供する必要があります。

JVM は、Java 仮想マシン プラットフォーム上で実行されるマルチスレッドの同時実行の問題にも直面します。そのため、Java プログラマーが基礎となるスレッドとレジスタ キャッシュ メモリ間の同期を直接制御することは不可能です。構文レベルでは、同期、揮発性、ロック メカニズム (同期ブロック、レディ キュー、ブロッキング キューなど) などのソリューションを開発者に提供する必要があります。これらの解決策は文法レベルにすぎませんが、本質的に理解する必要があります。各スレッドには独自の実行スペース (つまり、作業メモリ) があり、スレッドは実行中に変数を最初にコピーする必要があります。メインメモリに独自の作業メモリ空間を設定し、変数を操作します。読み取り、変更、代入などはすべて作業メモリ内で完了し、変数はメインメモリに書き戻されます。各スレッドはメイン メモリからデータを取得します。スレッド間のデータは見えません。たとえば、メイン メモリ変数 A の元の値は 1 で、スレッド 1 はメイン メモリから変数 A を取り出し、A の値を 2 に変更します。 、スレッド 1 は変数 A をメイン メモリに書き戻しません。スレッド 2 が変数 A の値を取得しても、値は 1 のままです

これは、共有変数にコピーがある場合の「可視性」の概念につながります。 1 つのスレッドがこれを変更した場合、複数のスレッドの作業メモリ。共有変数のコピー値が共有されている場合、他のスレッドは変更された値を見ることができる必要があります。これはマルチスレッドの可視性の問題です。

通常の変数の状況: たとえば、スレッド A が通常の変数の値を変更し、それをメイン メモリに書き戻します。スレッド A が書き戻しを完了した後、別のスレッド B がメイン メモリから新しい変数の値を読み取ります。その場合のみ、スレッド B に表示されます。

スレッド セーフを確保する方法

スレッド セーフなコードを記述することは、基本的に state (状態) へのアクセスを管理することであり、通常、それは共有された変更可能な状態です。ここでの状態とは、

オブジェクト

の変数(静的変数とインスタンス変数)です スレッド安全性の前提は、変数が複数のスレッドによってアクセスされるかどうかです。オブジェクトのスレッド安全性を確保するには、同期が必要です。変更可能な状態へのアクセスを調整する必要があります。これに失敗すると、データがダーティになり、その他の予期しない結果が生じます。複数のスレッドが特定の状態変数にアクセスし、そのうちの 1 つのスレッドがその変数に書き込む場合は常に、同期を使用してスレッドの変数へのアクセスを調整する必要があります。 Java の主な同期メカニズムは、排他ロックを提供する synchronized キーワードです。これに加えて、「同期」という用語には、揮発性変数、明示的ロック、およびアトミック変数の使用も含まれます。 正しく同期しないと、複数のスレッドが同じ変数にアクセスすると、プログラムが危険にさらされる可能性があります。これを修正するには 3 つの方法があります:

l スレッド間で変数を共有しない;

l 状態変数を不変にする; または

l 状態変数へのアクセスに同期を使用する。

volatile では、プログラムによる変数のすべての変更がメイン メモリに書き戻される必要があります。これにより、他のスレッド コースウェアの可視性の問題は解決されますが、データの一貫性は保証できません。特別な注意: アトミック操作: Java 仕様に従って、割り当てまたは基本型の戻り値操作はアトミック操作です。ただし、ここでの基本データ型には、long と double は含まれません。これは、JVM が認識する基本記憶単位は 32 ビットであり、long と double は両方とも 64 ビットで表されるためです。したがって、1 クロック サイクルで完了することはできません

平たく言えば、オブジェクトの状態は、複数のスレッドが特定の状態変数にアクセスするたびに、インスタンス フィールドや静的フィールドなどの状態変数に格納されるデータです。また、スレッドの 1 つが変数に書き込みます。このとき、同期を使用してスレッドの変数へのアクセスを調整する必要があります。同期ロックは、各 JAVA オブジェクトに常に 1 つだけあります。スレッドがロックを所有しています。

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。

1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。

(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁

如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

Synchronized关键字保证了数据读写一致和可见性等问题,但是他是一种阻塞的线程控制方法,在关键字使用期间,所有其他线程不能使用此变量,这就引出了一种叫做非阻塞同步的控制线程安全的需求;

ThreadLocal 解析

顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

Object wait()和notify()方法解析

Object的wait()和notify()、notifyAll()方法,使用一个对象作为锁,然后调用wait()就会挂起当前线程,同时释放对象锁;

notify()使用要首先获取对象锁,然后才能唤醒被挂起的线程(因为等待对象锁而挂起的)

notifyAll():唤醒在此对象监视器上等待的所有线程。

wait()在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

抛出:  IllegalMonitorStateException  - 如果当前线程不是此对象监视器的所有者

package com.taobao.concurrency;
public class WaitTest {
    public static String a = "";// 作为监视器对象

    public static void main(String[] args) throws InterruptedException {
        WaitTest wa = new WaitTest();
        TestTask task = wa.new TestTask();
        Thread t = new Thread(task);
        t.start();
        Thread.sleep(12000);
        for (int i = 5; i > 0; i--) {
            System.out.println("快唤醒挂起的线程************");
            Thread.sleep(1000);
        }
        System.out.println("收到,马上!唤醒挂起的线程************");
        synchronized (a) {
            a.notifyAll();
        }
    }

    class TestTask implements Runnable {

        @Override
        public void run() {
            synchronized (a) {
                try {
                    for (int i = 10; i > 0; i--) {
                        Thread.sleep(1000);
                        System.out.println("我在运行 ***************");
                    }
                    a.wait();
                    for (int i = 10; i > 0; i--) {
                        System.out.println("谢谢唤醒**********又开始运行了*******");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

用wait notify 解决生产者消费者问题代码:

package com.taobao.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class Meal {
    private final int orderNum;

    public Meal(int orderNum) {
        this.orderNum = orderNum;
    }

    public String toString() {
        return "Meal " + orderNum;
    }
}

class WaitPerson implements Runnable {
    private Restaurant restaurant;

    public WaitPerson(Restaurant r) {
        this.restaurant = r;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (restaurant.meal == null)
                        wait();// ..for the chef to produce a meal
                }
                System.out.println("WaitPerson got" + restaurant.meal);
                synchronized (restaurant.chef) {
                    restaurant.meal = null;
                    restaurant.chef.notifyAll();// ready for another
                }
            }
            TimeUnit.MICROSECONDS.sleep(100);
        } catch (InterruptedException e) {
            System.out.println("WaitPerson interrupted");
        }
    }
}

class Chef implements Runnable {
    private Restaurant restaurant;
    private int count = 0;

    public Chef(Restaurant r) {
        this.restaurant = r;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (restaurant.meal != null)
                        wait();// ...for the meal to be taken
                }
                if (++count == 10) {
                    System.out.println("Out of food,closing");
                    restaurant.exec.shutdownNow();
                }
                System.out.println("Order up!");
                synchronized (restaurant.waitPerson) {
                    restaurant.meal = new Meal(count);
                    restaurant.waitPerson.notifyAll();
                }

            }
        } catch (InterruptedException e) {
        }
    }
}

public class Restaurant {
    Meal meal;
    ExecutorService exec = Executors.newCachedThreadPool();
    WaitPerson waitPerson = new WaitPerson(this);
    Chef chef = new Chef(this);

    public Restaurant() {
        exec.execute(chef);
        exec.execute(waitPerson);
    }
    public static void main(String[] args) {
        new Restaurant();
    }

}

用ArrayBlockingQueue解决生产者消费者问题 ;默认使用的是非公平锁

take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止,若请求不到此线程被加入阻塞队列;

如果使用公平锁,当有内容可以消费时,会从队首取出消费者线程进行消费(即等待时间最长的线程)

add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则招聘异常

package com.taobao.concurrency;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestBlockingQueues {

    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
        Thread pro = new Thread(new Producer(queue), "生产者");
        pro.start();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Concumer(queue), "消费者 " + i);
            t.start();
        }

    }

}

class Producer implements Runnable {
    BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
                System.out.println("生产者生产食物, 食物编号为:" + i);
                queue.put(" 食物 " + i++);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("生产者被中断");
            }
        }
    }
}

class Concumer implements Runnable {
    BlockingQueue<String> queue;

    public Concumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println(Thread.currentThread().getName() + "消费:"
                        + queue.take());
            } catch (InterruptedException e) {
                System.out.println("消费者被中断");
            }
        }
    }
}


执行结果:
消费者 0 请求消费
消费者 2 请求消费
消费者 4 请求消费
消费者 6 请求消费
消费者 8 请求消费
消费者 5 请求消费
生产者生产食物, 食物编号为:0
消费者 0消费: 食物 0
消费者 1 请求消费
消费者 3 请求消费
消费者 7 请求消费
消费者 9 请求消费
消费者 0 请求消费
生产者生产食物, 食物编号为:1
消费者 2消费: 食物 1
消费者 2 请求消费
生产者生产食物, 食物编号为:2
消费者 4消费: 食物 2
消费者 4 请求消费
生产者生产食物, 食物编号为:3
消费者 6消费: 食物 3
消费者 6 请求消费
生产者生产食物, 食物编号为:4
消费者 8消费: 食物 4
消费者 8 请求消费
生产者生产食物, 食物编号为:5
消费者 5消费: 食物 5
消费者 5 请求消费
生产者生产食物, 食物编号为:6
消费者 1消费: 食物 6
消费者 1 请求消费
生产者生产食物, 食物编号为:7
消费者 3消费: 食物 7
消费者 3 请求消费
生产者生产食物, 食物编号为:8
消费者 7消费: 食物 8
消费者 7 请求消费
生产者生产食物, 食物编号为:9
消费者 9消费: 食物 9
消费者 9 请求消费

多个生产者,多个消费者

package com.taobao.concurrency;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestBlockingQueues {

    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
        for (int i = 0; i < 10; i++) {
            Thread pro = new Thread(new Producer(queue), "生产者" + i);
            pro.start();
        }
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Concumer(queue), "消费者 " + i);
            t.start();
        }

    }

}

class Producer implements Runnable {
    BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
               
                    System.out.println(Thread.currentThread().getName()
                            + "生产食物, 食物编号为:" + Thread.currentThread().getName()
                            + i);
                    queue.put(" 食物 " + Thread.currentThread().getName() + i++);
                    Thread.sleep(10000);
               
            } catch (InterruptedException e) {
                System.out.println("生产者被中断");
            }
        }
    }
}

class Concumer implements Runnable {
    BlockingQueue<String> queue;

    public Concumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " 请求消费");
            try {
                System.out.println(Thread.currentThread().getName() + "消费:"
                        + queue.take());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("消费者被中断");
            }
        }
    }
}

生产者0生产食物, 食物编号为:生产者00
生产者2生产食物, 食物编号为:生产者20
生产者1生产食物, 食物编号为:生产者10
生产者3生产食物, 食物编号为:生产者30
生产者4生产食物, 食物编号为:生产者40
生产者6生产食物, 食物编号为:生产者60
生产者8生产食物, 食物编号为:生产者80
生产者5生产食物, 食物编号为:生产者50
生产者7生产食物, 食物编号为:生产者70
生产者9生产食物, 食物编号为:生产者90
消费者 0 请求消费
消费者 0消费: 食物 生产者00
消费者 2 请求消费
消费者 2消费: 食物 生产者20
消费者 1 请求消费
消费者 1消费: 食物 生产者10
消费者 4 请求消费
消费者 4消费: 食物 生产者30
消费者 3 请求消费
消费者 6 请求消费
消费者 6消费: 食物 生产者40
消费者 3消费: 食物 生产者60
消费者 8 请求消费
消费者 8消费: 食物 生产者80
消费者 5 请求消费
消费者 5消费: 食物 生产者50
消费者 7 请求消费
消费者 7消费: 食物 生产者70
消费者 9 请求消费
消费者 9消费: 食物 生产者90
消费者 0 请求消费
消费者 1 请求消费
消费者 2 请求消费
消费者 4 请求消费
消费者 3 请求消费
消费者 5 请求消费
消费者 7 请求消费
消费者 9 请求消费
消费者 6 请求消费
消费者 8 请求消费
生产者0生产食物, 食物编号为:生产者01
消费者 0消费: 食物 生产者01
生产者2生产食物, 食物编号为:生产者21
生产者4生产食物, 食物编号为:生产者41
消费者 1消费: 食物 生产者21
生产者1生产食物, 食物编号为:生产者11
消费者 2消费: 食物 生产者41
消费者 4消费: 食物 生产者11
生产者3生产食物, 食物编号为:生产者31

条件队列解释:

Condition queuesare like the "toast is ready" bell on your toaster. If you are 
list
ening for it, you are notified promptly when your toast is ready and can drop what you are doing (or not, maybe you want to finish the newspaper first) and get your toast. If you are not listening for it (perhaps you went outside to get the newspaper), you could miss the notification, but on return to the kitchen you can observe the state of the toaster and either retrieve the toast if it is finished or start listening for the bell again if it is not.

基于条件的:多线程情况下,某个条件在某个时刻为假,不代表一直为假,可能到某个时刻就好了!

Lock 使用的默认 为非公平锁;condition对象继承了与之相关的锁的共平性特性,如果是公平的锁,线程会依照FIFO的顺序从Condition.wait中被释放;ArrayBlockingQueue中有一个比较不好的地方,生产者每次生产完之后,都要通知消费者,至于有没有性能损失TODO

【相关推荐】

1. 详解java 中valueOf方法实例

2. JMM java内存模型图文详解

3.详细介绍Java内存区域与内存溢出异常

4. JavaScript中的object转换函数toString()与valueOf()介绍_javascript技巧

以上がJava のスレッド メモリ モデルに関する詳細なチュートリアルの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。