Thread safety problems and solutions in java, thread state, inter-thread communication (thread waiting for wake-up mechanism)

thread safety

Overview:

When multiple threads access the shared data, conflicts will arise at this time (for example, if the business of selling goods is executed in multiple threads, the requirement is that after a certain thread is sold by a certain thread, other threads should no longer be able to sell this product, but By default, after being sold by a certain thread, other threads will still sell the goods, which is unreasonable here, but there are solutions), the conflict here refers to thread safety issues, and this problem can be avoided (always ensure that a thread is performing tasks, the current The task of other threads can only be executed after the thread task is executed), and the thread safety problem is realized by selling movie tickets as follows:

Create a ticket selling thread class by implementing the Runnable interface:

// 实现Runnbale接口的方式创建一个多线程实现类:
public class RunnableTmplClass implements Runnable {
    
    
    // 定义一个多个线程共享的数据(票):
    private int ticket = 10;
    // 重写run方法设置卖票任务:
    @Override
    public void run() {
    
    
        // 判断票是否存在,存在的话在售卖:
        for (int i = 0; i <= ticket; i++) {
    
    
            if (ticket > 0) {
    
    
                System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
                ticket--;
            }
        }
    }
}

Create three thread instances to sell tickets:

// 创建3个线程售卖票:
public class SellTicketTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建Runnable接口的实现类对象:
        RunnableTmplClass rt = new RunnableTmplClass();
        // 创建三个线程:
        Thread t1 = new Thread(rt);
        Thread t2 = new Thread(rt);
        Thread t3 = new Thread(rt);
        // 开启三个线程:
        t1.start();
        t2.start();
        t3.start();
    }
}

Print results:
Please add a picture description
Execution principle: Please add a picture description
Solve thread safety issues :

Thread synchronization can be used to solve thread safety problems. There are three methods of thread synchronization: synchronization code block, synchronization method, and lock mechanism

Synchronized code blocks solve thread safety issues:

// 解决线程安全的第一种方式:使用同步代码块(把同步代码块锁住,只让一个线程在同步代码块中执行)
// 注意:锁对象可以是任意对象、但是多个线程保证应该使用同一个锁对象
public class RunnableTmplClass implements Runnable {
    
    
    private int ticket = 10;
    // 在run外面创建一个公共的锁对象(同步锁,对象监视器):
    Object obj = new Object();
    @Override
    public void run() {
    
    
        for (int i = 0; i <= ticket; i++) {
    
    
            // 创建同步代码块:将会出现线程安全的代码放到同步代码块中即可:
            synchronized (obj) {
    
    
                if (ticket > 0) {
    
    
                    System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
                    ticket--;
                }
            };
        }
    }
}

Please add a picture description
Synchronous methods address thread safety:

// 解决线程安全的第二种方式:使用同步方法,把访问了共享数据的代码抽取出来放到一个方法中,此方法前面添加一个修饰符:synchronized,方法体里面写访问了共享数据的代码:
// 注意:还可以在方法体内写synchronized代码块解决线程安全问题
public class RunnableTmplMethod implements Runnable {
    
    
    private /*static*/ int ticket = 10;
    @Override
    public void run() {
    
    
        for (int i = 0; i <= ticket; i++) {
    
    
            cellTicket();
            // cellTicketSecond();
        }
    }
    // 定义一个同步方法,第一种方式:
    public /*static ,这里static可以加上,提前定义的变量也是静态的才可以访问得到变量*/ synchronized void cellTicket() {
    
    
        if (ticket > 0) {
    
    
            System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
            ticket--;
        }
    };
    // 定义一个同步方法,第二种方式:
    public void cellTicketSecond() {
    
    
        synchronized (this) {
    
    
            if (ticket > 0) {
    
    
                System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
                ticket--;
            }
        };
    };
}

The lock lock solves the thread safety problem:

Using locks to solve thread safety issues is more advanced than synchronized.

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

// 解决线程安全的第三种方式:使用lock锁
// 使用步骤:1.在成员位置创建一个ReentrantLock对象 2.在可能会出现安全问题的代码前调用lock方法获取锁 3.在可能会出现安全问题的代码后调用unlock方法释放锁
public class RunnableTmplLock implements Runnable {
    
    
    private int ticket = 10;
    // 创建锁对象:
    Lock lk = new ReentrantLock();
    @Override
    public void run() {
    
    
        // for (int i = 0; i <= ticket; i++) {
    
    
        //     // 调用锁:
        //     lk.lock();
        //     if (ticket > 0) {
    
    
        //         System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
        //         ticket--;
        //     };
        //     // 释放锁:
        //    lk.unlock();
        // }

        // 下面方式为比较安全的,无论是否报错,都会释放锁
        for (int i = 0; i <= ticket; i++) {
    
    
            // 调用锁:
            lk.lock();
            if (ticket > 0) {
    
    
                try {
    
    
                    System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");// 没有处理线程安全前,这里打印的结果是:某个票可能被多次售卖,某个票可能一次也没有售卖
                    ticket--;
                } finally {
    
    
                    lk.unlock();
                }
            };
        }
    }
}

Thread state :

Thread state overview:

The state of the thread here can be understood as the life cycle of the state, that is, the process from the creation of the state to the end of the state. The state can be divided into 6 states in turn: new Therad() new stage—Runnable operation stage (here there is another situation where the CPU is not preempted, called blocked blocking state, blocking state and running state can be switched between each other)—Terminated Death state—Timed_waiting dormancy state—waiting infinite waiting state
Please add a picture description
Dormancy state case:

public class SleepStatic implements Runnable {
    
    
    private int ticket = 10;
    @Override
    public void run() {
    
    
        for (int i = 0; i <= ticket; i++) {
    
    
            if (ticket > 0) {
    
    
                try {
    
    
                    // 调用Thread类的sleep对程序进入休眠状态休眠2000毫秒在进入其他状态
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
                System.out.println("线程:" + Thread.currentThread().getName() + "正在售卖第:" + ticket + "号票。");
                ticket--;
            }
        }
    }
}

Infinite wait state (communication between threads):

A thread that is waiting indefinitely for another thread to perform a particular wakeup action is in this state.

The execution process of the infinite waiting state: there is a consumer thread that needs a certain demand. At this time, the consumer will call the wait method to enter the infinite waiting state. The producer thread starts to process the demand. When the result is processed by the producer, it can call the notify method to notify Wake up the consumer and start consuming.

import java.util.Random;

// 创建一个消费者线程:告知生产者线程需要的结果种类和数量,调用wait方法,放弃cpu执行权,进入Waiting无限等待状态
// 创建一个生产者线程:处理消费者需要的结果,当结果处理好后,调用notify方法唤醒消费者来消费结果。
// 注意: 消费者线程和生产者线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个执行;同步使用的锁对象必须是唯一的,只有锁对象才能调用wait和notify方法
public class WaitNotifyStatic {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个随机数变量
        final int[] num = {
    
    0};
        // 创建一个唯一的锁对象:
        Object obj = new Object();

        // 1.创建一个消费者线程:
        new Thread() {
    
    
            @Override
            public void run() {
    
    
                // 使用同步技术保证消费者线程和生产者线程只能有一个执行:
                synchronized (obj) {
    
    
                    // 向生产者发起业务需求:
                    System.out.println("A:消费者要求生产者处理业务,生成一个随机数:");
                    try {
    
    
                        // 调用wait方法进入无限休眠状态(这里会抛异常,使用try/catch处理即可):
                        System.out.println("B:消费者调用wait方法开启无限等待状态中");
                        obj.wait(); // 里面可以传入一个毫秒值,此时和sleep大概一样,当传入的时间小于生产者处理业务所用时间时,此时消费者会提前醒来进入runnable/blocked状态
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    // 生产者唤醒消费者后执行的代码:
                    System.out.println("E:消费者被生产者唤醒开始消费生产者所处理的结果:" + num[0]);
                };
            };
        }.start(); // 调用start方法执行消费者线程

        // 2.创建一个生产者线程:
        new Thread() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    // 生产者需要花费5秒钟处理业务(同样会抛异常,使用try/catch处理即可):
                    Thread.sleep(5000);
                    Random randoms = new Random();
                    num[0] = randoms.nextInt(100);
                    System.out.println("C:生产者花费了一段时间处理结果,生成一个随机数:" + num[0]);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
                // 在同步代码块中唤醒消费者,继续执行消费者中wait之后的代码:
                synchronized (obj) {
    
    
                    System.out.println("D:生产者调用notify方法唤醒消费者:");
                    obj.notify(); // 当有多个消费者线程时,notify只能随机唤醒一个等待消费者线程,而obj.notifyAll可以唤醒所有等待线程
                };
            };
        }.start(); // 调用start方法执行消费者线程
    }
    // 提示:依次打印了a-e的结果,且执行完B后等待了一段时间后再开始执行后面代码
}

Wait for the wake-up mechanism (inter-thread communication) :

The concept of inter-thread communication: Multiple threads process the same resource (here often refers to a whole set of business), but the processing actions (tasks of the threads) are different. When multiple threads execute concurrently, the cpu switches threads randomly by default, but sometimes we need to let them execute regularly. At this time, some coordinated communication between multiple threads is required to complete multiple threads. Handle a whole set of business.

Guaranteeing inter-thread communication is an effective use of resources: When multiple threads process the same data, to avoid contention for the same shared variable, we can use some means (such as judging whether a certain data exists, which thread to execute when it exists, and if it does not exist Which thread is executed at the time) so that each thread can effectively use resources. This method is called the waiting wake-up mechanism.

The method of waiting for wakeup: that is, wait, notify, notifyAll used above.

When the wait method is called, the current thread enters the wait set and waits for other threads to execute notify to release the thread from the wait set and re-enter the dispatch queue ready queue. Entering the wait set will not consume CPU at this time, nor will it To compete for locks.

notify: Select the thread that has waited the longest in the wait set of the notified object to release.

notifyAll: Release all threads in the wait set of the notified object.

Note: Even if only one waiting thread is notified, the notified thread will not resume execution immediately, because the place where it was interrupted is in the synchronization block, and at this moment it no longer holds the lock, so it needs to try to acquire the lock again ( may face competition from other threads), and only after the lock is successfully acquired can the execution be resumed at the place after the wait method was originally called.

The following is a case where a user buys buns:

Bun class:

// 1.创建一个包子类:
public class BaoZiClass {
    
    
    // 包子皮:
    String pi;
    // 包子馅:
    String xian;
    // 包子状态:是否有包子,用于线程间通信状态的判断
    boolean flag = false;
}

Baozi shop category:

// 2.创建一个包子铺类,用来生产包子的线程:当包子实例flag有包子时,包子铺调用wait方法进入等待状态;当flag没有包子时,包子铺生产包子,当包子生产好后修改flag状态为有并唤醒吃货线程吃包子
// 注意:包子铺线程和包子线程之间是互斥关系,两者只能有一个同时执行,因此需要使用同步技术

// 因为包子铺是一个线程,所以可以继承Thread类,并重写里面的run方法设置线程任务:
public class BaoZiPuClass extends Thread{
    
    
    // 定义一个包子变量:
    private BaoZiClass bz;

    // 使用包子铺带参数构造方法,为包子变量赋值:
    public BaoZiPuClass(BaoZiClass bz) {
    
    
        this.bz = bz;
    }

    // 重写run方法生产包子:
    @Override
    public void run() {
    
    
        // 定义一个序号,用于生产两种包子:
        int count = 0;

        // 使用循环让包子铺一直生产包子:
        while (true) {
    
    
            // 使用同步代码块解决线程安全问题:
            synchronized (bz) {
    
    
                // 如果有包子,调用wait方法进入等待,否则生产包子:
                if (bz.flag == true) {
    
    
                    try {
    
    
                        bz.wait();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                };

                // 生产两种类型的包子:
                if (count % 2 == 0) {
    
    
                    bz.pi = "薄皮";
                    bz.xian = "粉丝馅";
                } else {
    
    
                    bz.pi = "厚皮";
                    bz.xian = "猪肉线";
                };

                System.out.println("A:包子铺正在生产第"+count+"个包子:"+bz.pi+","+bz.xian);
                count++;

                // 为了模拟真实环境,设置生产包子花费的时间为2秒钟:
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }

                // 当休眠2s后包子生产好了,修改包子的状态为有:
                bz.flag = true;

                // 唤醒吃货线程:
                System.out.println("B:包子铺已经生产好包子了,开始唤醒吃货吃包子:");
                bz.notify();
            };
        }
    };
}

Food category:

// 3.创建一个吃货类(消费者)线程:对包子的状态进行判断,有包子的话吃,没有的话调用await方法等待。
public class ChiHuoClass extends Thread {
    
    
    // 设置一个包子变量:
    private BaoZiClass bz;
    // 使用构造方法为包子变量赋值:
    public ChiHuoClass(BaoZiClass bz) {
    
    
        this.bz = bz;
    };

    // 重写run方法设置吃包子的任务:
    @Override
    public void run() {
    
    
        // 使用死循环一直吃包子:
        while(true){
    
    
            // 使用同步代码块解决线程安全问题,这里锁对象和包子铺中的锁对象为同一个对象:
            synchronized (bz) {
    
    
                // 如果没有包子,调用wait进入等待状态
                if (bz.flag == false) {
    
    
                    try {
    
    
                        bz.wait();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
                // 开吃包子:
                System.out.println("C:吃货正在吃:"+bz.pi+","+bz.xian+"的包子");

                // 修改包子状态:
                bz.flag = false;

                // 唤醒包子铺生产包子:
                System.out.println("D:吃货唤醒包子铺生产包子:");
                bz.notify();

                System.out.println("-----------------------------");
            }
        }
    };
}

Test foodies and eat steamed buns:

// 4.测试吃货买包子及包子铺生产包子等业务:
public class TestClass {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个包子:
        BaoZiClass bz = new BaoZiClass();

        // 创建一个包子铺,并开启线程:
        new BaoZiPuClass(bz).start();

        // 创建一个吃货,并开启线程:
        new ChiHuoClass(bz).start();

        // 提示打印结果为:包子铺生产一个包子,吃货就吃掉一个包子
    }
}

Tips: The pictures and other materials in this article come from the Internet. If there is any infringement, please send an email to the mailbox: [email protected] to contact the author to delete it.
Author: sea of ​​bitterness

Guess you like

Origin blog.csdn.net/weixin_46758988/article/details/128523965