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

  • Post author:
  • Post category:其他










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


2.java内存模型


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


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


5.同步代码块(synchronized)


​同步代码块的格式:


锁的分类:


用锁解决案例问题:


​同步的好处和弊端:




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票甚至负票呢?先来了解一下Java内存模型吧!!!




2.java内存模型




Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来,也就是读)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。



简单点就是,请看图!!!!!








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







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





总结一下会出现安全隐患的标准:


得是多线程环境、有共享数据、有多条语句操作共享数据!



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





我们无法让其没有共享数据更没有办法让其不处于多线程的环境!如果不是多线程,那当然不会出现安全隐患的问题啦!那么只能从多条语句操作共享数据下手了!那在操作共享数据的时候,能不能一次只让一个进入呢?当然可以!所以就出现了锁!


把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可,操作完了,锁立马释放掉!


就不会出现线程安全问题啦!



5.同步代码块(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票和负票的情况了!!!!这可是个好东西啊!!!









同步的好处和弊端:






同步的出现


解决了多线程的安全问题


。 其弊端就是,当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会


降低程序的运行效率







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



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



版权声明:本文为naoguoteng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。