자바 멀티 스레딩 연구 노트 _ 판매 티켓 사례 분석

하나, 동기화 코드 블록

먼저 코드를 게시하십시오.

public class MyRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if(ticket == 0){
                break;
            }else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }    
                ticket--;
                System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        MyRunnable mr1 = new MyRunnable();

        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr1);
        Thread t3 = new Thread(mr1);

        t1.setName("#窗口一#");
        t2.setName("##窗口二##");
        t3.setName("###窗口三###");

        t1.start();
        t2.start();
        t3.start();
    }
}

여기의 코드는 설명이 필요한 콘텐츠를 이끌어 내기 위해 이전 블로그 게시물 ( 티켓 판매 사례 구현)과 다릅니다 .

차이점은 while 루프의 판단 조건이 true로 변경되어 스레드 수면이 증가한다는 것입니다.

문제 : 위 코드를 실행하는 동안 중복 투표와 반대 투표가 발생합니다.

그 이유는 슬립 프로세스 동안 스레드 A의 제어가 다른 스레드 (예 : B)에게 주어지기 때문입니다.이 프로세스에서 스레드 B는 공유 데이터 티켓을 수정하므로 스레드 A가 인쇄 할 때 티켓은 이미 그렇지 않습니다. 티켓이 바뀌 었다고 그는 바꿨습니다.

그리고 티켓이 0으로 설정되면 판단이 이루어지기 전에 데이터 값이 변경되어 무한 루프가 발생할 가능성이 큽니다.

if 판단 조건을 <= 0으로 변경하면 무한 루프가 나타나지 않지만 여전히 중복 투표와 반대 투표가 있습니다.

따라서 다른 스레드가 실행 중일 때 공유 데이터를 조작 할 수 없으면이 상황이 해결됩니다.

이 경우 동기화 된 () 메서드 가 필요합니다 . 동기화 된 코드 블록을 실현하기 위해 공유 데이터에서 작동하는 여러 코드를 잠급니다.

synchronized(任意对象){
    操作共享数据的多条语句
}

기본적으로 코드를 실행할 스레드가있는 한 잠금이 열려 있습니다.

스레드가 실행되면 잠금이 자동으로 열립니다.

동기화의 장단점 :

  • 이점 : 다중 스레드 데이터 보안 문제를 해결합니다.
  • 단점 : 스레드가 많으면 모든 스레드가 동기화 잠금을 판단하므로 리소스 집약적이며 사실상 프로그램 실행의 효율성이 저하됩니다.

둘째, 잠금 개체는 고유합니다.

위의 티켓 판매 사례에서 사용 된 구현 방법은 Runnable 인터페이스를 상속하는 것이며 스레드간에 동일한 매개 변수가 사용되므로 잠금도 공유됩니다.

각 스레드에 자체 잠금이있는 경우에도 중복 투표 및 부정 투표 문제가 있습니다. 즉, 스레드가 안전하지 않게됩니다. (Thread 클래스를 상속하여 멀티 스레딩을 구현하는 경우)


public class MyThread extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized(obj){
                if(ticket <= 0){
                    break;
                }else {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    ticket--;

                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}

이는 MyThread 클래스의 티켓 변수와 Object 개체를 정적으로 설정하여 모든 MyThread 클래스 개체가 정적 변수를 공유하도록하기위한 것입니다.

세, 동기화 방법

동기화 방법의 잠금 개체는 다음과 같습니다.

동기 정적 메서드의 잠금 개체는 다음과 같습니다. class.class



public class MyRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {

        while (true) {
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean res = synchronizedMethod();
                if(res){
                    break;
                }
            }

            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized(this){
                    if(ticket == 0){
                        break;
                    }else {

                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                    }
                }
            }
        }

    }

    private synchronized boolean synchronizedMethod() {
        if (ticket == 0) {
            return true;
        } else {

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            ticket--;
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            return false;
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        MyRunnable mr1 = new MyRunnable();

        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr1);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();

    }
}

참고 :이 코드에서 멀티 스레딩을 구현하는 방법은 Runnable 인터페이스를 상속하는 것입니다. 두 스레드의 매개 변수는 동일하며 MyRunnable의 객체 mr1에있는 run () 메서드가 실행되므로 잠금 객체도 동일합니다.

동기 정적 방법 :

public class MyRunnable implements Runnable{
    //静态方法只能访问静态变量,所以要加上static进行修饰
    private static int ticket = 100;
    @Override
    public void run() {

        while (true) {
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean res = synchronizedMethod();
                if(res){
                    break;
                }
            }

            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                //这里的对象需要进行修改
                synchronized(MyRunnable.class){
                    if(ticket == 0){
                        break;
                    }else {

                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                    }
                }
            }
        }

    }

    private static synchronized boolean synchronizedMethod() {
        if (ticket == 0) {
            return true;
        } else {

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            ticket--;
            System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
            return false;
        }
    }
}

네, 잠금

동기화 코드 블록의 잠금 객체 문제와 동기화 방법을 이해할 수는 있지만 잠금이 추가 된 위치와 잠금이 해제되는 위치를 직접 확인하지 못했습니다. 잠금 및 해제 방법을보다 명확하게 표현하기 위해 JDK5 이후 제공 될 예정입니다. 새로운 잠금 객체 잠금

Lock 구현은 동기화 된 메서드 및 문을 사용하여 얻을 수있는 것보다 더 넓은 범위의 잠금 작업을 제공하고 Lock은 잠금을 획득하고 해제하는 방법을 제공합니다.

  • void lock () : 잠금 획득
  • void unlock () : 잠금 해제

Lock은 인터페이스이며 직접 인스턴스화 할 수 없습니다. 여기서 구현 클래스 ReentrantLock을 사용하여 인스턴스화합니다.

import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {
    private static int ticket = 100;
//    private static Object obj = new Object();

    ReentrantLock rlock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            
//            synchronized(obj){
            try {
                rlock.lock();
                if (ticket <= 0) {
                    break;
                } else {

                    Thread.sleep(100);
                    ticket--;

                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rlock.unlock();
            }
//            }
        }
    }
}

다섯, 교착 상태

교착 상태는 잠금 중첩으로 인해 발생하므로 교착 상태를 피하기 위해 잠금 중첩을 작성하지 않는 것이 좋습니다.

추천

출처blog.csdn.net/qq_43191910/article/details/114987823