并发编程-(2)线程安全问题

目录

1、什么叫线程安全

当多个线程访问某个类(对象或者方法)时,这个类始终能表现出正确的行为,那么这个类(对象或方法)是安全的。

2、多窗口买票案例

package com.fly.thread_demo.demo_2;

/**
 *  模拟火车站买票:一共库存 100张票 ,两个窗口同时卖票
 */
public class ThreadTrain {
    /**
     * 车票库存100张
     */
    private int count  = 100;

    /**
     * 买票方法
     */
    public  void sell(){
        if (count > 0 ){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count -- ;
            System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
        }
    }

    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        /**
         * 窗口一
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (threadTrain.count > 0 ){
                    threadTrain.sell();
                }
            }
        },"窗口一").start();
        /**
         * 窗口二
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (threadTrain.count > 0 ){
                    threadTrain.sell();
                }
            }
        },"窗口二").start();
    }
}

我们一共有100张火车票,但是最后我们卖了101张,这就是线程安全问题

3、如何解决线程安全问题

3.1、锁的特征

只能同时被一个线程持有。

3.2、内置锁(synchronized)

保证线程的原子性,当线程进入方法时,自动获取锁,一旦锁被获取,其他线程在执行该方法时候会等待,当程序执行完毕后就会释放锁,后面排队的线程会抢这把锁,没抢到的线程会继续等待。

3.3、解决火车票问题

3.3.1、同步代码块

    
    private Object obj = new Object();
    /**
     * 买票方法 使用静态代码快的方式 
     */
    public void sell(){
        
        //让一个obj对象成为一把锁 
        synchronized (obj){

            if (count > 0 ){
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count -- ;
                System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张                火车票,卖完还剩"+count+"张!");
            }
        }

    }

public  void sell(){
    //这里我们每次使用不同的对象做为锁
    synchronized (new Object()){

        if (count > 0 ){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count -- ;
            System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
        }
    }

}

结论一: 在多线程中,要使用同步,必须使用同一把锁。

另:

​ 如果使用String 类型的锁,我们改变了String对象的值,就会让锁发生改变

​ 如果我们用对象锁,我们改变对象属性的值,不会改变锁

3.3.2、同步方法

/**
 * 买票方法  方法加    synchronized  关键字
 */
public synchronized void sell(){
    if (count > 0 ){
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count -- ;
        System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
    }
}

4、同步方法锁的进阶

这里我们就只演示结论,不演示不成功的来对比了。

package com.fly.thread_demo.demo_2;

/**
 * 非静态同步方法  和  静态同步方法
 */
public class ThreadSafe {
    private Object obj = new Object();

    public void printNum_1(){
        synchronized (obj){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
        }
    }
    public void printNum_2(){
        synchronized (obj){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
        }
    }

    /**
     * 现在有两个线程,分别调用printNum_1和printNum_2方法
     * 如果我们给printNum_1  和  printNum_2  加锁
     * 我们使用前面的结论:如果使用同一把锁  两个方法将会同步  如果不是同一把锁 将不会同步
     */
    public static void main(String[] args) {

        ThreadSafe threadSafe = new ThreadSafe();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_2();
            }
        }).start();

    }
}

在线程Thread-0执行完毕后再执行线程Thread-1,说明前面的结论没有错

4.1、非静态同步方法

package com.fly.thread_demo.demo_2;

/**
 * 非静态同步方法  和  静态同步方法
 */
public class ThreadSafe {
    private Object obj = new Object();

    /**
     * 在非静态方法上加synchronized锁
     */
    public synchronized void printNum_1(){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
    }

    /**
     * 使用当前对象作为锁
     */
    public void printNum_2(){
        synchronized (this){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
        }
    }

    /**
     * 现在有两个线程,分别调用printNum_1和printNum_2方法
     * 如果我们给printNum_1  和  printNum_2  加锁
     * 如果使用同一把锁  他们将会同步  如果不是同一把锁 将不会同步
     */
    public static void main(String[] args) {

        ThreadSafe threadSafe = new ThreadSafe();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_2();
            }
        }).start();

    }
}

结论二: 非静态方法上加synchronize锁,其实使用的是this锁,也就是把当前对象作为锁

4.2、静态同步方法

package com.fly.thread_demo.demo_2;

/**
 * 非静态同步方法  和  静态同步方法
 */
public class ThreadSafe {
    private Object obj = new Object();

    /**
     * 在静态方法上加synchronized锁
     */
    public synchronized static void printNum_1(){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
    }

    /**
     * 使用当前类的 字节码文件 作为锁
     */
    public void printNum_2(){
        synchronized (ThreadSafe.class){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+ ": i = " + i);
            }
        }
    }

    /**
     * 现在有两个线程,分别调用printNum_1和printNum_2方法
     * 如果我们给printNum_1  和  printNum_2  加锁
     * 如果使用同一把锁  他们将会同步  如果不是同一把锁 将不会同步
     */
    public static void main(String[] args) {

        ThreadSafe threadSafe = new ThreadSafe();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSafe.printNum_2();
            }
        }).start();

    }
}

结论三:在静态方法上加synchronize锁,其实是把当前类的字节码文件当做锁。

5、死锁

5.1、死锁案例

package com.fly.thread_demo.demo_2;

/**
 *  死锁  一般是由锁的嵌套引起的
 *  小明和小红打架,小明揪着小红的头发,小红揪着小明的头发,
 *  小明要等小红放手自己才会放手,小红也要等着小明放手自己才会放手
 *  这样他们永远也不会松手
 */
public class DeathThread implements Runnable{
    private String tag;
    public void setTag(String tag){
        this.tag = tag;
    }
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    @Override
    public void run() {

        if ("小明怒了".equals(tag)) {
            synchronized (obj1) {
                System.out.println("小明揪住了小红的头发...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("因为小红放开了小明,所以小明放开了小红...");
                }
            }
        }

        if ("小红怒了".equals(tag)) {
            synchronized (obj2) {
                System.out.println("小红揪住了小明的头发...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("因为小明放开了小红,所以小红放开了小明...");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeathThread dt1 = new DeathThread();
        dt1.setTag("小明怒了");
        DeathThread dt2 = new DeathThread();
        dt2.setTag("小红怒了");

        Thread t1 = new Thread(dt1);
        Thread t2 = new Thread(dt2);

        t1.start();

        t2.start();

    }
}

程序一直没结束,但是小明和小红都不放手,这是因为小明拿到先拿到了obj1锁,然后休眠了1秒钟,与此同时,小红拿到了obj2锁,也休眠了1秒钟,一秒钟过后,小明要obj2锁才能放手,但是obj2锁在小红手上,要等小红执行完毕才能释放obj2锁,但是小红想要执行完毕要先获得obj1锁,同样obj1锁在小明手上,这样就造成了死锁问题

5.2、快速定位死锁位置

我们一般使用jdk工具

  1. 打开控制台输入jconsole

  2. 这个时候会打开Java简直和管理控制台

  1. 打开对应的类

  1. 选择不安全链接

  2. 找到对应的线程名称,查看详情

猜你喜欢

转载自www.cnblogs.com/bigfly277/p/10201214.html