스레드 안전성, 상태 및 동기화 메커니즘

1. 스레드 안전성

스레드 안전성은 각 프로그램 실행의 결과가 단일 스레드 실행의 결과와 동일하고 다른 변수의 값이 예상과 동일 함을 의미합니다. 그러나 안전하지 않은 상황은 여러 스레드가 특정 리소스를 동시에 실행하여 예측할 수없는 결과를 초래할 수 있다는 것입니다.

스레드의 보안 문제를 설명하기 위해 사례를 사용합니다. 영화관에서 티켓을 판매하고 영화관에서 티켓을 판매하는 과정을 시뮬레이션합니다. 이 영화에는 100 개의 좌석이 있습니다 (이 영화는 100 장만 판매 할 수 있습니다). 영화관의 티켓 창을 시뮬레이션하고 여러 창에서 동시에 영화 티켓을 판매한다는 사실을 알아 봅니다 (여러 창에서이 100 개 티켓을 함께 판매). 창이 필요하고 스레드 개체가 시뮬레이션에 사용됩니다. 티켓이 필요합니다. , Runnable 인터페이스 하위 클래스가 시뮬레이션에 사용됩니다.

package com.itheima.demo06.ThreadSafe;
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    
    
    public static void main(String[] args) {
    
    
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

package com.itheima.demo06.ThreadSafe;
/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;


    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

여기에 사진 설명 삽입

스레드 안전성 문제는 전역 변수와 정적 변수로 인해 발생합니다. 각 스레드의 전역 변수 및 정적 변수에 대한 읽기 작업 만 있고 쓰기 작업은없는 경우 일반적으로이 전역 변수는 스레드로부터 안전합니다. 여러 스레드가 동시에 쓰기 작업을 수행하는 경우 일반적으로 스레드 동기화를 고려해야합니다. 그렇지 않으면 스레드 안전성이 영향을받을 수 있습니다.

2. 스레드 동기화

창 1 스레드가 작업에 들어가면 창 2 및 창 3 스레드는 외부에서만 대기 할 수 있으며 창 1 작업이 종료되고 창 1, 창 2 및 창 3은 실행할 코드를 입력 할 수 있습니다. 즉, 스레드가 공유 자원을 수정하면 다른 스레드는 자원을 수정할 수 없으며 수정이 완료되고 동기화 된 후에는 CPU 자원을 잡고 해당 작업을 완료하여 데이터 동기화를 보장하고 문제를 해결합니다. 스레드 불안정 현상.

동기화 메커니즘

  1. 코드 블록을 동기화합니다.
  2. 동기화 방법.
  3. 잠금 장치.

2.1 동기화 코드 블록

동기화 된 코드 블록 : 동기화 된 키워드는 메서드의 블록에서 사용할 수 있으며, 이는이 블록의 리소스 만 독점적으로 액세스 할 수 있음을 의미합니다. 체재:

동기화 됨 (동기화 잠금) {동기화해야하는 코드}

동기화 잠금 : 개체의 동기화 잠금은 개체에 잠금을 표시하는 것으로 상상할 수있는 개념 일뿐입니다.

  1. 잠금 개체는 모든 유형이 될 수 있습니다.
  2. 여러 스레드 개체는 동일한 잠금을 사용해야합니다.
    참고 : 언제든지 최대 하나의 스레드가 동기화 잠금을 소유 할 수 있으며 잠금을받은 사람은 코드 블록에 들어가고 다른 스레드는 외부에서만 대기 할 수 있습니다 (BLOCKED).
    동기 코드 블록을 사용하여 코드를 해결합니다.
package com.itheima.demo07.Synchronized;
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的一种方案:使用同步代码块
    格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

    注意:
        1.通过代码块中的锁对象,可以使用任意的对象
        2.但是必须保证多个线程使用的锁对象是同一个
        3.锁对象作用:
            把同步代码块锁住,只让一个线程在同步代码块中执行
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
           //同步代码块
            synchronized (obj){
    
    
                //先判断票是否存在
                if(ticket>0){
    
    
                    //提高安全问题出现的概率,让程序睡眠
                    try {
    
    
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }

                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

2.2 동기화 방법

동기화 방식 : 동기화 방식으로 수정 된 방식을 동기화 방식 이라고하는데, 이는 A 스레드가 해당 메서드를 실행할 때 다른 스레드는 메서드 외부에서만 대기 할 수 있도록합니다.
체재:

공용 동기화 된 void method () {스레드 안전 문제를 일으킬 수있는 코드}

동기화 잠금의 정의
비 정적 메서드의 경우 동기화 잠금은 이것입니다.
정적 메서드의 경우 현재 메서드가있는 클래스의 바이트 코드 객체 (클래스 이름 .class)를 사용합니다.

package com.itheima.demo08.Synchronized;
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.在方法上添加synchronized修饰符

    格式:定义方法的格式
    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private static int ticket = 100;


    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            payTicketStatic();
        }
    }

    /*
        静态的同步方法
        锁对象是谁?
        不能是this
        this是创建对象之后产生的,静态方法优先于对象
        静态方法的锁对象是本类的class属性-->class文件对象(反射)
     */
    public static /*synchronized*/ void payTicketStatic(){
    
    
        synchronized (RunnableImpl.class){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象 new RunnableImpl()
        也是就是this
     */
    public /*synchronized*/ void payTicket(){
    
    
        synchronized (this){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }
}

2.3 잠금 메커니즘

java.util.concurrent.locks.Lock이 메커니즘은 동기화 된 코드 블록 및 동기화 된 메서드보다 더 넓은 범위의 잠금 작업을 제공합니다. 동기화 된 코드 블록 / 동기화 된 메서드는 더 강력하고 객체 지향적 일뿐만 아니라 모든 기능 잠금을 갖습니다.
잠금 잠금은 동기 잠금이라고도하며 잠금 및 잠금 해제 방법은 다음과 같이 변경됩니다.
public void lock () : 동기 잠금 추가.
public void unlock () : 동기화 잠금을 해제합니다.
다음과 같이 사용

package com.itheima.demo09.Lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的三种方案:使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    Lock接口中的方法:
        void lock()获取锁。
        void unlock()  释放锁。
    java.util.concurrent.locks.ReentrantLock implements Lock接口


    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }

   
}

3. 스레드 상태

여기에 사진 설명 삽입

3.1 정기 대기 (정기 대기)

public class MyThread extends Thread {
    
     
	public void run() {
    
     
		for (int i = 0; i < 100; i++) {
    
     
			if ((i) % 10 == 0) {
    
     
				System.out.println("‐‐‐‐‐‐‐" + i);
				}
			System.out.print(i); 
			try {
    
    Thread.sleep(1000); 
			System.out.print(" 线程睡眠1秒!\n"); 
			} catch (InterruptedException e) {
    
     e.printStackTrace(); } 
		} 
	}
	public static void main(String[] args) 
	{
    
     new MyThread().start(); } 
}

수면 방법의 사용이 여전히 매우 간단한 경우를 통해 알 수 있습니다. 다음 사항을 기억해야합니다.

  1. TIMED_WAITING 상태로 들어가는 일반적인 상황은 sleep 메서드를 호출하는 것입니다. 이는 별도의 스레드에서도 호출 할 수 있으며 반드시 협력 관계를 가질 필요는 없습니다.
  2. 다른 스레드가 실행할 기회를 갖도록하려면 스레드 run ()에 Thread.sleep () 호출을 넣을 수 있습니다. 그러면 스레드가 실행되는 동안 휴면 상태가됩니다.
  3. 잠자는 잠금과 관련이 없으며 스레드가 만료되면 자동으로 깨어나 Runnable 상태로 돌아갑니다.

팁 : sleep ()에 지정된 시간은 스레드가 실행되지 않는 가장 짧은 시간입니다. 따라서 sleep () 메서드는 스레드가 sleep이 만료 된 직후에 실행을 시작한다고 보장 할 수 없습니다.

3.2 BLOCKED (잠금 차단됨)

Blocked 상태는 API에서 다음과 같이 도입됩니다. 모니터 잠금 (잠금 오브젝트)을 차단하고 대기중인 스레드가이 상태에 있습니다.
예를 들어 스레드 A와 스레드 B는 코드에서 동일한 잠금을 사용합니다. 스레드 A가 잠금을 획득하고 스레드 A가 실행 가능 상태가되면 스레드 B는 차단 된 잠금 상태가됩니다.

3.3 대기 (무한 대기)

Wating 상태는 API에 다음과 같이 도입됩니다. 다른 스레드가 특수 (wake-up) 작업을 수행 할 때까지 무기한 대기중인 스레드가이 상태에 있습니다.

package com.itheima.demo10.WaitAndNotify;
/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
        创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

    注意:
        顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        同步使用的锁对象必须保证唯一
        只有锁对象才能调用wait和notify方法

    Obejct类中的方法
    void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
    void notify()
          唤醒在此对象监视器上等待的单个线程。
          会继续执行wait方法之后的代码
 */
public class Demo01WaitAndNotify {
    
    
    public static void main(String[] args) {
    
    
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
    
    
            @Override
            public void run() {
    
    
               //一直等着买包子
               while(true){
    
    
                   //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                   synchronized (obj){
    
    
                       System.out.println("告知老板要的包子的种类和数量");
                       //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                       try {
    
    
                           obj.wait();
                       } catch (InterruptedException e) {
    
    
                           e.printStackTrace();
                       }
                       //唤醒之后执行的代码
                       System.out.println("包子已经做好了,开吃!");
                       System.out.println("---------------------------------------");
                   }
               }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                //一直做包子
                while (true){
    
    
                    //花了5秒做包子
                    try {
    
    
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
    
    
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

실제로 대기 상태는 스레드의 작업이 아니라 여러 스레드 간의 통신을 반영하여 여러 스레드 간의 협력 관계로 이해할 수 있습니다. 여러 스레드는 잠금을 위해 싸우며 서로 협력 관계가 있습니다. .

A 및 B 스레드와 같은 여러 스레드가 협력 할 때 A 스레드가 Runnable 상태에서 wait () 메서드를 호출하면 A 스레드는 대기 (무한 대기) 상태가되고 동기화 잠금을 잃게됩니다. 이때 B 스레드가 동기화 잠금을 획득하고 실행 상태에서 notify () 메서드를 호출하면 무기한 대기중인 A 스레드가 깨어납니다. 깨우기에주의를 기울여야합니다. 잠금 객체를 획득하면 A 스레드는 깨어 난 후 Runnable 상태로 들어가고 잠금 객체가 획득되지 않으면 Blocked 상태로 들어갑니다.
여기에 사진 설명 삽입

추천

출처blog.csdn.net/david2000999/article/details/113859621