欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-17 14:05:00
...

*在某个时刻,某个核中只能执行一个进程,但是进程可以拆分成多个线程,只能执行其中一个线程,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类里。

线程状态

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

相关标签: 多线程 java