Thread safety, state and synchronization mechanism

1. Thread Safety

Thread safety means that the result of each program run is the same as the result of a single-threaded run, and the values ​​of other variables are the same as expected. But the unsafe situation is that multiple threads may execute a certain resource at the same time, resulting in unpredictable consequences.

We use a case to demonstrate the security problem of threads: Cinemas sell tickets, and we simulate the process of selling tickets in cinemas. There are 100 seats in this movie (only 100 tickets can be sold for this movie). Let's simulate the ticket window of a movie theater, and realize that multiple windows sell movie tickets at the same time (multiple windows sell these 100 tickets together). The window is needed, and the thread object is used to simulate; the ticket is needed, and the Runnable interface subclass is used to simulate.

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--;
            }
        }
    }
}

Insert picture description here

Thread safety issues are caused by global variables and static variables. If there are only read operations on global variables and static variables in each thread, but no write operations, in general, this global variable is thread-safe; if multiple threads perform write operations at the same time, thread synchronization generally needs to be considered, otherwise Otherwise, thread safety may be affected.

2. Thread synchronization

When the window 1 thread enters the operation, the window 2 and window 3 threads can only wait outside, and the window 1 operation ends, and the window 1, window 2 and window 3 have the opportunity to enter the code to execute. That is to say, when a thread modifies a shared resource, other threads cannot modify the resource. After the modification is completed and synchronized, it can grab the CPU resource and complete the corresponding operation, which ensures the synchronization of data and solves the problem of thread insecurity. phenomenon.

Synchronization mechanism

  1. Synchronize code blocks.
  2. Synchronization method.
  3. Lock mechanism.

2.1 Synchronization code block

Synchronized code block: The synchronized keyword can be used in a block in the method, which means that only the resources of this block can be accessed exclusively. format:

synchronized (synchronization lock) {code that needs to be synchronized}

Synchronization lock: The synchronization lock of an object is just a concept, which can be imagined as marking a lock on the object.

  1. The lock object can be of any type.
  2. Multiple thread objects need to use the same lock.
    Note: At any time, at most one thread is allowed to own a synchronization lock, whoever gets the lock enters the code block, and other threads can only wait outside (BLOCKED).
    Use synchronous code blocks to solve the code:
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 Synchronization method

Synchronization method : The method modified by synchronized is called the synchronization method, which ensures that when the A thread executes the method, other threads can only wait outside the method.
format:

public synchronized void method(){Code that may cause thread safety issues}

Who is the synchronization lock?
For non-static methods, the synchronization lock is this.
For the static method, we use the bytecode object (class name.class) of the class where the current method is located.

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 Locking mechanism

java.util.concurrent.locks.LockThe mechanism provides a wider range of locking operations than synchronized code blocks and synchronized methods. Synchronized code blocks/synchronized methods have all the functions Lock, in addition to being more powerful and more object-oriented.
Lock lock is also called synchronization lock. The methods of
adding and releasing locks are changed as follows: public void lock(): Add synchronization lock.
public void unlock(): Release the synchronization lock.
Use as follows

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. Thread status

Insert picture description here

3.1 Timed Waiting (Timed Waiting)

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(); } 
}

It can be found through the case that the use of the sleep method is still very simple. We need to remember the following points:

  1. A common situation to enter the TIMED_WAITING state is to call the sleep method, which can also be called by a separate thread, and it does not necessarily have to have a cooperative relationship.
  2. In order to allow other threads to have a chance to execute, you can put the call of Thread.sleep() in the thread run(). This will ensure that the thread sleeps during its execution
  3. The sleep has nothing to do with the lock. The thread wakes up automatically when it expires and returns to the Runnable state.

Tips: The time specified in sleep() is the shortest time that the thread will not run. Therefore, the sleep() method cannot guarantee that the thread will start executing immediately after the sleep expires.

3.2 BLOCKED (lock blocked)

The Blocked state is introduced in the API as: a thread that is blocking and waiting for a monitor lock (lock object) is in this state.
For example, thread A and thread B use the same lock in their code. If thread A acquires the lock and thread A enters the Runnable state, then thread B enters the Blocked lock state.

3.3 Waiting (infinite waiting)

The Wating state is introduced in the API as: a thread that is waiting indefinitely for another thread to perform a special (wake-up) action is in this state.

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();
    }
}

In fact, the waiting state is not an operation of a thread. It reflects the communication between multiple threads, which can be understood as a cooperative relationship between multiple threads. Multiple threads will fight for locks, and there is a cooperative relationship between each other.

When multiple threads cooperate, such as A and B threads, if the A thread calls the wait() method in the Runnable state, then the A thread enters the Waiting (infinite waiting) state and loses the synchronization lock. If at this time the B thread acquires the synchronization lock and calls the notify() method in the running state, then the A thread that is waiting indefinitely will be awakened. The attention is to wake up. If the lock object is acquired, then the A thread will enter the Runnable (runnable) state after waking up; if the lock object is not acquired, then it will enter the Blocked (lock blocked state).
Insert picture description here

Guess you like

Origin blog.csdn.net/david2000999/article/details/113859621