Java线程安全\死锁问题与解决方案

买票问题产生线程安全问题

问题产生背景

  • 场景1: 一个窗口卖100张票,单线程不会产生共享数据也不会导致线程安全问题
  • 场景2: 1号窗口卖(0-33) 2号窗口卖(34-66) 3号窗口卖(67-100)票 不存在共享数据也不会导致线程安全问题
  • 场景3: 1-3号窗口同时卖1-100号票 存在共享数据会导致线程安全问题

代码模拟

  • RunnableImpl.java
package com.su27.ThreadSafe;

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--)+"张票");
            }
        }
    }
}

  • Demo.java
package com.su27.ThreadSafe;

// 模拟卖票案例
// 创建3个线程,同时开启,对共享票出售
public class Demo {
    public static void main(String[] args) {
        // 创建Runnale接口的实现类对象
        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();
    }
}

Thread-2--->正在卖第3张票
Thread-2--->正在卖第1张票
Thread-1--->正在卖第-1张票 // 线程安全问题 出现超卖
Thread-0--->正在卖第0张票 // 线程安全问题 出现超卖

分析产生原因

  • 卖票产生重复不存在
  • 同时3个线程度同一共享票数

解决线程安全问题

同步代码块

  • 使用synchronized关键字传入锁对象并将 有线程安全问题的代码块 放入同步代码块
package com.su27.SolveThreadSafe;
/*
解决线程安全问题方案一:使用同步代码块
格式:
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--)+"张票");
                }
            }
        }
    }
}

同步技术原理

  • 同步代码块线程,没有执行完毕不释放锁,同步外线程没有锁进不去同步代码块,保证始终只能有一个线程执行,频繁上/下锁,降低效率,但避免线程安全问题

同步方法

  • 定义synchronized修饰的方法将有线程安全问题的代码放入其中
package com.su27.SolveThreadSafe1;
/*
解决线程安全问题方案一:使用同步方法
使用步骤:
1. 把访问了共享数据代码抽取出来,放到方法中
2. 在方法上加synchronized修饰符
格式: 修饰符 synchronized 返回类型 方法名(参数列表){
    共享数据代码
}
注意:
1.通过代码块中的锁对象,可以使用任意对象
2. 但必须保证多个线程使用锁对象是同一个
3. 锁对象作用:
    把同步代码块锁住,只让一个线程在同步代码块中执行
 */
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;

    // 创建一锁对象
    Object obj = new Object();

    // 设置线程任务*卖票
    @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--)+"张票");
        }
    }
}
  • 注意 同步方法的锁对象就是实现对象 new RunnableImpl()也就是this
  • 验证this例子
/*定义同步方法*/
    public /*synchronized*/ void payTicket() {
        synchronized(this){
            // 先判断票是否存在
            if(ticket>0){
                // 提高线程安全问题出现概率
                try {
                    Thread.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                // 票存在 卖票ticket--
                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+(ticket--)+"张票");
            }
        }

    }

死锁问题

  • A B同时执行 A首先加1锁 B首先加2锁 A下一步要2锁 B要1锁 产生锁的嵌套
    就发生死锁情况,互相因为得不到对方手中的资源无法释放当前各种资源。
package com.su27.DeadLock;

public class Demo {
    public static void main(String[] args) {

        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while (true){
                synchronized (objA){
                    synchronized (objB){
                        System.out.println("小康同学在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while (true){
                synchronized (objB){
                    synchronized (objA){
                        System.out.println("小微同学在走路");
                    }
                }
            }
        }).start();
    }
}

おすすめ

転載: blog.csdn.net/weixin_42043407/article/details/120340491