java 线程安全问题,解决线程安全问题——同步代码块,同步方法,Lock锁,Object类中wait方法,notify方法。等待唤醒案例。

1、线程安全问题

线程出现安全问题的代码:

/*
    实现卖票案例
 */
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--;
            }
        }
    }
}

主类:

/*
    模拟卖票案例
    创建三个线程,同时开启,对共享的票进行出售
 */
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();
    }
}

运行截图:
在这里插入图片描述
三个线程卖了同一张票,即安全问题。

2、解决线程安全问题—同步代码块

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实现互斥访问。
格式:

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

主代码不变。

3、解决线程安全问题——同步方法

解决线程安全的第二种方案:使用同步方法
使用步骤:
1、把访问了共享数据的代码抽取出来,放到一个方法中
2、在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法民(参数列表) {
可能出现线程安全问题的代码(访问了共享数据的代码)
}

代码:

/*
    实现卖票案例出现了线程安全问题

    解决线程安全的第二种方案:使用同步方法
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private int ticket = 100;

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

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

4、解决线程安全问题——Lock锁

Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock( ) :加同步锁
public void unlock( ) :释放同步锁
代码及使用步骤如下:

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

/*
    实现卖票案例
    出现了线程安全问题

    解决线程安全问题的第三种方案:使用Lock锁
    java.util.concurrent.Locks.Lock接口
    Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
    Lock接口中的方法:
        public void lock( ) :加同步锁
        public 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);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

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

            // 3、可能会出现安全问题的代码后调用lock接口中的方法unlock释放锁
            l.unlock();
        }
    }
}

5、几种线程的状态

Timed Waiting 计时等待
Blocked 锁阻塞状态
Waiting 无限等待

6、等待唤醒案例

代码:

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

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

    Object类中的方法
    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();

    }
}

运行截图:

在这里插入图片描述

7、Object类中wait带参方法和notifyAll方法

进入到TimeWaiting(计时等待)有两种方式
1、使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2、使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
void notify( )唤醒单个线程
void notifyAll( )唤醒所有线程。

猜你喜欢

转载自blog.csdn.net/Gaoju12138/article/details/109518216
今日推荐