>  기사  >  Java  >  Java의 스레드 메모리 모델에 대한 자세한 튜토리얼

Java의 스레드 메모리 모델에 대한 자세한 튜토리얼

零下一度
零下一度원래의
2017-05-25 16:14:54929검색


java 스레드 메모리 모델

모든 스레드는 주 메모리를 공유하고, 각 스레드에는 자체 작업 메모리가 있습니다

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

스레드 안전성의 이유

스레드의 작업 메모리는 CPU 레지스터 및 캐시에 대한 추상적인 설명입니다. 오늘날의 컴퓨터에서는 CPU가 계산을 수행할 때 항상 데이터를 가져오지는 않습니다. 데이터 읽기, 데이터 읽기 순서 우선순위는 등록-캐시-메모리입니다. 스레드는 CPU를 소비합니다. 스레드가 계산하는 동안 원본 데이터는 메모리에서 자주 읽혀질 수 있습니다. 이러한 데이터는 스레드가 계산을 완료한 후 다시 기록되어야 합니다. 적절할 때 기억에 남습니다. 여러 스레드가 동시에 특정 메모리 데이터를 읽고 쓸 때 원자성, 질서성 및 가시성이라는 세 가지 특성과 관련된 다중 스레드 동시성 문제가 발생합니다. 멀티스레딩을 지원하는 플랫폼은 이 문제에 직면하게 되며, 멀티스레딩 플랫폼에서 실행되는 멀티스레딩을 지원하는 언어는 이 문제에 대한 솔루션을 제공해야 합니다.

JVM은 또한 다중 스레드 동시성 문제에 직면하게 됩니다. Java 프로그램은 Java 가상 머신 플랫폼에서 실행되며 기본 스레드와 레지스터 캐시 메모리 간의 상호 작용을 직접 제어하는 ​​것은 불가능합니다. 동기화, 그러면 Java는 개발자에게 구문 수준의 솔루션을 제공해야 합니다. 이 솔루션에는 동기화, 휘발성, 잠금 메커니즘(예: 동기화된 블록, 준비 대기열, 차단 대기열) 등이 있습니다. 이러한 해결 방법은 문법적 수준일 뿐이지만 본질적으로 이해해야 합니다.

각 스레드에는 자체 실행 공간(예: 작업 메모리)이 있습니다. 스레드가 실행 시 변수를 먼저 사용해야 합니다. be 메인 메모리에서 자신의 작업 메모리 공간을 복사한 다음 변수를 조작합니다(읽기, 수정, 값 할당 등). 작업이 완료된 후 변수는 다시 쓰기됩니다. 주 메모리

각 스레드는 모두 주 메모리에서 데이터를 가져오며 스레드 간의 데이터는 보이지 않습니다. 예를 들어 주 메모리 변수 A의 원래 값은 1이고 스레드 1은 주 메모리에서 변수 A를 가져옵니다. , A의 값을 2로 수정하고 스레드 1에서 변수 A가 주 메모리에 다시 기록되지 않을 때 스레드 2에서 얻은 변수 A의 값은 여전히 ​​1입니다.

이는 다음과 같은 개념으로 이어집니다. "가시성": 공유 변수가 여러 스레드의 작업 메모리에 있는 경우 공유 변수에 복사본이 있을 때 한 스레드가 공유 변수의 복사본 값을 수정하면 다른 스레드가 수정된 값을 볼 수 있어야 합니다. 다중 스레드 가시성 문제.

일반 변수 상황: 예를 들어 스레드 A는 일반 변수의 값을 수정한 다음 이를 주 메모리에 다시 씁니다. 스레드 A가 다시 쓰기 작업을 완료한 후 다른 스레드 B가 주 메모리에서 읽습니다. 스레드 B에는 새 변수의 값만 표시됩니다.

스레드 안전성을 보장하는 방법

스레드로부터 안전한 코드를 작성하는 것은 본질적으로 상태를 관리하는 것입니다. (상태) 액세스, 일반적으로 공유, 변경 가능한 상태입니다. 여기서 상태는 객체 의 변수입니다( 정적 변수 및 인스턴스 변수)

스레드 안전성의 전제는 변수가 여러 스레드에서 액세스되는지 여부입니다. 개체 보안의 스레드에서는 변경 가능한 상태에 대한 액세스를 조정하기 위해 동기화를 사용해야 합니다. 그렇게 하지 않으면 더티 데이터 및 기타 예측할 수 없는 결과가 발생할 수 있습니다. 둘 이상의 스레드가 주어진 상태 변수에 액세스하고 스레드 중 하나가 변수에 쓸 때마다 동기화를 사용하여 변수에 대한 스레드의 액세스를 조정해야 합니다. Java의 기본 동기화 메커니즘은 배타적 잠금을 제공하는 동기화 키워드입니다. 이 외에도 "동기화"라는 용어에는 휘발성 변수, 명시적 잠금 및 원자 변수의 사용도 포함됩니다.

적절한 동기화 없이 여러 스레드가 동일한 변수에 액세스하면 프로그램이 위험할 수 있습니다. 이를 해결하는 방법에는 3가지가 있습니다.

l 스레드 간에 변수를 공유하지 마세요.

l 상태 변수에 액세스할 때마다

l 동기화를 사용하세요.

Volatile에서는 프로그램에 의한 모든 변수 수정이 주 메모리에 다시 기록되어야 합니다. 이는 다른 스레드 코스웨어의 가시성 문제를 해결하지만 데이터 일관성을 보장할 수는 없습니다. 기본 유형에 대한 Java 사양, 할당 또는 반환 값 작업은 원자적 작업입니다. 그러나 여기의 기본 데이터 유형에는 long과 double이 포함되지 않습니다. 왜냐하면 JVM에서 보는 기본 저장 단위는 32비트이고 long과 double은 모두 64비트로 표시되기 때문입니다. 따라서 하나의 클록 주기에 완료될 수 없습니다.

일반인의 관점에서 개체의 상태는 두 개 이상의 스레드가 액세스할 때마다 인스턴스 필드 또는 정적 필드와 같은 상태 변수에 저장되는 데이터입니다. 주어진 상태 변수. 그리고 스레드 중 하나가 변수에 기록됩니다. 이때 변수에 대한 스레드의 액세스를 조정하려면 동기화를 사용해야 합니다.

동기화 잠금: 각 JAVA 객체에는 언제든지 하나의 동기화 잠금만 있습니다. , 최대 하나의 스레드가 이 잠금을 소유할 수 있습니다.

当一个线程试图访问带有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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.