Java基础:多线程安全问题及其解决方法、线程的六种状态

多线程安全问题

线程安全问题的产生

线程安全问题:

  • 单线程程序是不会出现线程安全问题
  • 多线程程序没有访问共享数据不会产生问题
  • 多线程访问了共享的数据,就会产出线程安全问题

模拟卖票:

  • 创建三个线程,同时开启,对共享的票进行出售
  • 会发现当我们多个线访问同一个资源时,且多个线程中对资源有写的操作,就容易出现线程安全问题。(重复票与不存在票)
  • 要解决线程并发访问同一资源的安全性问题,也就是解决重复票与不存在票问题,Java提供了同步机制来解决

注意:

  • 线程安全问题是不能产生的,我们可以让一个线程在访问共享数据时,无论是否失去了cpu的执行权;让其他线程只能等待,等待当前线程卖完票,其他线程再进行卖票
  • 保证:只使用一个线程在卖票
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}
//实现卖票案例
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--;
                }
        }
    }
}

在这里插入图片描述在这里插入图片描述

解决这种线程安全问题的方法

方法一

解决线程安全问题的第一种方案:使用同步代码块
格式:

  •   synchronized(锁对象){
    
  •       可能会出现线程安全问题的代码(访问了共享数据的代码)
    
  • }

注意:

  •   1.通过代码块中的锁对象,可以使用任意的对象
    
  •   2.但是必须保证多个线程使用的锁对象是同一个
    
  •   3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
    

在这里插入图片描述

public class RunnableImpl implements Runnable {
    //创建一个锁对象
    Object obj = new Object();
    //定义一个多线程共享的票源
    private int ticket =100;
    //设置线程任务:卖票
    @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--;
                }
            }

        }

    }
}
方法二

解决线程安全问题的第二种方案:使用同步方法
使用步骤:

  •  1.把访问了共享数据的代码抽取出来,放在一个方法中去
    
  •  2.在方法上添加synchronized修饰符
    
  • 格式:定义方法的格式
  •   修饰符 synchronized 返回值类型 方法名(参数列表){
    
  •  可能会出现线程安全问题的代码(访问了共享数据的代码)
    
  • }
    
 public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket =100;
    //设置线程任务:卖票
    @Override
    public void run() {
        while (true){
            payTicket();
        }
    }
    //定义一个同步方法

    /*同步方法也会把方法的内部的代码锁住
    * 只让一个线程执行
    * 同步方法的锁对象是谁
    * 就是实现类对象 new RunnableImpl()
    * 也就是this*/
    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--;
            }
    }
}
方法三

解决线程安全问题的第三种方案:使用lock锁

  • java.util.concurrent.locks.Lock接口
  • Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
  • Lock接口中的方法:
  •  void lock()获取锁
    
  •  void unlock()释放锁
    

java.util.concurrent.locks.ReentrantLock implement Lock 接口
使用步骤:

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

    //设置线程任务:卖票
    @Override
    public void run() {
        while (true){
            l.lock();
            if (ticket>0){
                //提高安全问题出现的概率
                try {
                    Thread.sleep(10);
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    l.unlock();//无论是否发生异常,都会将锁释放,增加程序的效率
                }
            }
        }
    }

       /* while (true){
            l.lock();
                if (ticket>0){
                    //提高安全问题出现的概率
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
            l.unlock();
        }
    }*/
}

线程的状态

在这里插入图片描述
线程有六种状态,其中Waiting状态需要使用Object类中的wait方法进入等待状态,notify方法唤醒等待的线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值