当前位置:网站首页>线程安全问题以及其解决方法

线程安全问题以及其解决方法

2022-08-02 14:32:00 naoguaziteng

1.为什么会出现线程安全问题?

2.判断一个多线程应用程序是否有问题的标准:

3.如何解决多线程安全问题呢?

4.同步代码块(synchronized)

同步代码块的格式: 

锁的分类:

用锁解决案例问题:

同步的好处和弊端:    

5.死锁问题


1.为什么会出现线程安全问题?

实例代码:

public class MyTest {
    public static void main(String[] args) {
        SellTicketRunnable runnable = new SellTicketRunnable();
        //创建了三个线程
        Thread th1 = new Thread(runnable);
        Thread th2 = new Thread(runnable);
        Thread th3 = new Thread(runnable);
        //给三个线程重新命名
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        //开启线程
        th1.start();
        th2.start();
        th3.start();
    }
}


public class SellTicketRunnable implements Runnable {
    static int num = 100; //三个线程共享了这个数据(一共有100张票)
    @Override
    public void run() {
        while (true) {
                if (num > 0) {
                    try {
                        Thread.sleep(20);  //模拟以下延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
                }
        }
    }
}

现实中售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟.于是在代码中加入了一点延迟!这个不影响什么的!看起来没有什么问题!部分运行结果如下:

       

看到这运行结果,就发现问题了!现实中的售票情况怎么可能会几个窗口出售同一张票呢?甚至有的时候还会出现出售0票和负票的情况.这就是出现了线程安全问题!

那么重点来啦!为什么会出现线程安全问题呢?为什么会几个窗口出售同一张票,0票甚至负票呢?请看图!!!!!!

 

 如上这样毫无规矩,随机性的抢占,当然会坏事啦!试想一下,去医院看病,这般你争我抢,直接毁灭吧!!!说个玩笑话!这样的抢占,就引起了安全问题!怎么解决,相信友友们已经有想法了.

2.判断一个多线程应用程序是否有问题的标准:

总结一下会出现安全隐患的标准:  得是多线程环境、有共享数据、有多条语句操作共享数据!

3.如何解决多线程安全问题呢?

        我们无法让其没有共享数据更没有办法让其不处于多线程的环境!如果不是多线程,那当然不会出现安全隐患的问题啦!那么只能从多条语句操作共享数据下手了!那在操作共享数据的时候,能不能一次只让一个进入呢?当然可以!所以就出现了锁!  把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可,操作完了,锁立马释放掉!  就不会出现线程安全问题啦!

4.同步代码块(synchronized)

同步代码块的格式: 

synchronized(对象){  
要被同步的代码 ; 
} 

注意: 不能在大括号里直接new 对象, new 了 就没效果了!

这个同步代码块保证数据的安全性的一个主要因素就是这个对象. 注意这个对象要定义为静态成员变量才能被所有线程共享.这个对象其实就是一把锁. 这个对象习惯叫做监视器.

锁的分类:

内置锁

        每个java对象都可以用做一个实现同步的锁,这些锁便被称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁. 同步代码代码块上的锁亦是如此. 这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

对象锁和类锁

        java的对象锁和类锁在锁的概念上基本上和内置锁是一致的. 但是两个锁实际是有很大区别的,对象锁是用于对象实例方法或者一个对象实例上的,而类锁是用于类的静态方法或者一个类的class对象上的。类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。还有一点必须注意的哟!也就是类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.

哈哈哈!不要晕乎乎!这个锁的分类只是想让友友们了解一下而已啦!

用锁解决案例问题:

我们用锁解决一下上面案例出现的线程安全问题!!!!

public class MyTest {
    public static void main(String[] args) {
        SellTicketRunnable runnable = new SellTicketRunnable();
        //创建了三个线程
        Thread th1 = new Thread(runnable);
        Thread th2 = new Thread(runnable);
        Thread th3 = new Thread(runnable);
        //给三个线程重新命名
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        //开启线程
        th1.start();
        th2.start();
        th3.start();
    }
}


public class SellTicketRunnable implements Runnable {
    static int num = 100; //三个线程共享
    static Object obj = new Object();     //上锁对象,静态成员变量

    @Override
    public void run() {
        while (true) {
            //th1 th2  th3中任意一个线程,一旦进入同步代码块,就会被加锁,其他没被加锁的对象便会在此等待
            synchronized (obj) {      //这个就是锁
                if (num > 0) {
                    try {
                        Thread.sleep(20);         //模拟延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
                }
            }  //出了同步代码块,就会释放锁
        }
    }
}

现在查看结果就不会出现几个窗口出售同一张票以及出售0票和负票的情况了!!!!这可是个好东西啊!!!

同步的好处和弊端:    

同步的出现  解决了多线程的安全问题。 其弊端就是,当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会  降低程序的运行效率

5.死锁问题

如果出现了同步嵌套,就会很容易产生死锁问题.是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.简单来说死锁是  两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态.

public class MyThread extends Thread {
    private boolean flag;
    public MyThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (LockUtils.objA) {
                System.out.println("true线程进来了持有ObjA锁");
                synchronized (LockUtils.objB) {
                    System.out.println("true线程进来了持有ObjB锁");
                }
            }
        } else {
            synchronized (LockUtils.objB) {
                System.out.println("false线程进来了持有ObjB锁");
                synchronized (LockUtils.objA) {
                    System.out.println("false线程进来了持有ObjA锁");
                }
            }
        }
    }
}


public interface LockUtils {
    //定义了两把锁对象
    Object objA = new Object();
    Object objB = new Object();
}


public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        //创建了两个线程
        MyThread th1 = new MyThread(true);
        MyThread th2 = new MyThread(false);
        //开启线程
        th1.start();
        th2.start();
    }
}

运行结果便是如此!你等我,我等你.害!孽缘啊!所以一定要注意切勿造成如此"孽缘"!!!!

(小编也在努力学习更多哟!以后再慢慢分享的啦!)

希望对友友们有所帮助!!!!

原网站

版权声明
本文为[naoguaziteng]所创,转载请带上原文链接,感谢
https://blog.csdn.net/naoguoteng/article/details/126102962