多线程并发安全问题与死锁解决

*在某个时刻,某个核中只能执行一个进程,但是进程可以拆分成多个线程,只能执行其中一个线程,CPU只能执行一个线程,CPU会切换执行线程
*

多线程的好处

线程要么和CPU进行交互要么和硬件进行交互,当线程在和硬件进行交互时CPU处于空闲状态,引入多线程提高CPU的利用率(理论上可以提高到100%)

创建多线程的方式

1.继承Thread类,创建描述线程执行信息的类的对象,调用父类的start方法来开启线程
2.实现Runnable接口,由接口实现类对象来构建Thread类对象调用start方法来开启线程(常用)

==由于多线程之间存在相互抢占(CPU的执行权),抢占发生在代码每一步,导致多线程数据并发安全问题(重复、负数、跳过等等错误数据)

多线程并发安全问题的解决方式(加锁)

1.同步代码块锁
synchronized(锁对象){} 根据指定的锁对象来共享线程对象对象,保证共享进来线程对象执行代码块内容时不会发生抢占

锁对象
当前参与的线程对象共享进来
方法区资源(所有的线程对象都能共享进来)
this(当参与进来的线程对象被同一个Runnable实现类对象共享时)

2.同步方法锁
保证共享进来线程对象执行方法内容时不会发生抢占
如果是在非静态方法上加上锁,默认锁对象就是this
如果是在静态方法上加上锁,默认锁对象就是当前类.class(方法区资源)

public class SellTicketDemo2 {
    public static void main(String[] args) throws IOException {
        //加载配置文件里的count的对应值
        Properties p=new Properties();
        p.load(new FileReader("ticket.properties"));

        //获取键对应的值
        String count=p.getProperty("count");

        //创建代表票类的对象
        Ticket t=new Ticket();
        //设置票数
        t.setCount(Integer.parseInt(count));

        //创建一个Seller类(Runnable实现类)对象---一个售票系统
        //四个对象都是由相同的t对象构建而来,共享t对象的票数
        Seller1 s=new Seller1(t);

        //创建四个线程对象
        //指定线程对象的名称
        //四个线程对象共享同一个Runnable实现类对象,锁对象就可以指定为this
        Thread t1=new Thread(s,"A").start();
        Thread t2=new Thread(s,"B").start();
        Thread t3=new Thread(s,"C").start();
        Thread t4=new Thread(s,"D").start();
    }
}


//代表线程的任务信息(卖票过程)
class Seller1 implements Runnable{
    //表示票数
    //static int count=100;

    //引入代表票的类的对象
    Ticket t;

    //有参构造---保证创建的对象共享同一个Ticket类的对象
    public Seller1(Ticket t){
        this.t=t;
    }

    //卖票具体过程--重写run方法
    //如果加在非静态方法上默认锁对象就是this
    //如果加在静态方法上默认锁对象就是当前类.class(方法区资源)
    @Override
    public synchronized void run() {

        //循环实现重复卖票过程
        while (true) {
            //同步代码块锁
            //()--需要填入锁对象
            //锁对象---提供一个对象来共享线程对象,被共享进来的线程对象保证
            //执行代码块区域内容时某个时刻只能一个线程对象来执行(不会发生抢占)
            //synchronized (t) {//把当前参与的线程对象共享进来
                //指定都是方法区的资源(方法区的资源是被所有的线程对象共享)
                //synchronized (Math.class) {//Seller.class String.class
          
            //synchronized (this) {//可以当作锁对象
                //循环结束条件
                if(t.getCount()==0)
                    break;

                //卖一张票---设置新的票数
                t.setCount(t.getCount() - 1);
                //输出卖票信息
                //Thread.currentThread()---当前正在执行线程对象
                //getName()---线程对象的名称
                System.out.println(Thread.currentThread().getName() +
                        "卖了一张票,还剩" + t.getCount() + "张票。。。");

                //线程休眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            //}
        }
    }
}

解决死锁问题

死锁—由于锁的嵌套导致死锁问题,通过死锁检测让其中一个线程对象先执行解决死锁问题

下面为一个生产消费模型案例
每次生产的商品数量是随机值,但是保证总的剩余商品数量不能大于1000,每次消费也是随机值,生产和消费要交替出现。

**等待唤醒机制(结合锁使用)
通过wait、notif以及notifyAll和标志位来控制线程对象执行顺序
**

public class NotifyAllWaitDemo {
    public static void main(String[] args) {
        //创建代表商品类对象
        Pruduct p=new Pruduct();
        
        new Thread(new Pruductor1(p)).start();
        new Thread(new Pruductor1(p)).start();
        new Thread(new Pruductor1(p)).start();
        new Thread(new Consumer1(p)).start();
        new Thread(new Consumer1(p)).start();
        new Thread(new Consumer1(p)).start();
    }
}
//代表生产者---描述生产过程
class Pruductor1 implements Runnable{
    //声明商品类的对象
    Pruduct p;

    //通过有参构造传入商品类对象
    public Pruductor1(Pruduct p){
        this.p=p;
    }

    //重写方法---详细生产的过程
    @Override
    public void run() {
        //保证一直生产
        while (true){

            //加锁
            synchronized (p){
                //线程等待
                //保证每次都能进行判断
                while (p.getFalg()==false)
                    try {
                        p.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                //此次最大生产商品数量
                int max=1000-p.getCount();
                //随机生产商品数量
                int count=(int) (Math.random()*(max+1));
                //设置新的剩余商品数量
                p.setCount(p.getCount()+count);
                //输出
                System.out.println("生产了"+count+"个商品,还剩余"
                        +p.getCount()+"个商品。。。");

                //唤醒线程
                //p.notify();
                //唤醒所有线程对象
                p.notifyAll();
                //改变布尔值
                p.setFalg(false);
            }
        } }
}

//=============================================================================================
//代表消费者---描述消费过程
class Consumer1 implements Runnable{
    //声明商品类的对象
    Pruduct p;

    //通过有参构造传入商品类对象
    public Consumer1(Pruduct p){
        this.p=p;
    }

    //重写方法---详细消费的过程
    @Override
    public void run() {
        //保证一直消费
        while (true){

            //加锁
            synchronized (p){

                //线程等待
                while (p.getFalg()==true)
                    try {
                        p.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                //随机消费商品数量
                int count=(int) (Math.random()*(p.getCount()+1));
                //设置新的剩余商品数量
                p.setCount(p.getCount()-count);
                //输出
                System.out.println("消费了"+count+"个商品,还剩余"
                        +p.getCount()+"个商品。。。");

                //唤醒等待线程对象
               // p.notify();
                //唤醒所有线程对象
                p.notifyAll();
                //改变布尔值
                p.setFalg(true);
            }
        }
    }}

sleep方法与wait方法的区别

1.sleep方法一定要指定休眠时间(毫秒),到点自然醒。休眠期间,如果没有锁是释放CPU执行权,但是当有锁时依然会释放CPU执行权不会释放锁对象。定义在Thread类里静态方法。

2.wait方法可以指定等待时间也可以不指定,如果指定等待时间到点自然醒,如果没有指定等待时间需要手动唤醒。在等待期间会释放CPU执行权和锁对象。wait方法定义在Object类里。

线程状态

计算机生成了可选文字:阻塞创建一一一拯全

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值