15036188778

您所在位置: 首页> 学习课程> java培训 | Java编程(一):并发编程之线性安全

java培训 | Java编程(一):并发编程之线性安全

发布百知教育 来源:学习课程 2019-09-05

导读:要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(可由多线程同时访问)和可变的(变量值在其生命周期内会发生变化)状态的访问。


一、什么是线程安全


本节我们将用一个示例来回答线程安全是什么,具体示例请参考以下实现代码。

public class UnsafeStates {

    private int states = 0;


    public int getStates() {

        states++;

        return states;

    }


    public static void main(String[] args) {

        final UnsafeStates states = new UnsafeStates();


        new Thread(){

            public void run() {

                for (int i = 0; i < 1000000; i++) {

                    System.out.println(states.getStates());

                }

            }

        }.start();


        new Thread(){

            public void run() {

                for (int i = 0; i < 1000000; i++) {

                    System.out.println(states.getStates());

                }

            }

        }.start();

    }

}


getStates()将UnSafeStates类的私有变量states递增后返回states值。我们启动了2个线程,每个线程循环调用1百万次getStates()方法,并将其返回值打印出来。按照我们的预期,线程间不应该产生影响,即最后运行的结果的最后一个值应为2000000。然后,最后的运行结果却是1999993(多次运行,结果还不一样,例如1999946等),具体输出结果请参考图1所示。


java培训


图1 UnSafeStates类运行结果


出现此类情况的原因有很多种,最常见是线程1进入方法后拿到states值,还未改变其值,结果线程2也进入了,导致2线程拿到的states值是一样的(可参看图2的处理流程)。而这个结果也表明了UnSafeStates类的getStates()方法不是线程安全的。



java培训



图2 UnSafeStates.getStates()的错误执行情况


根据这个示例,我们总结下什么是线程安全:当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么就可以说这个类是线程安全的。


二、如何解决线程安全性问题


既然存在线程安全性问题,那么肯定需要有对应的方案来解决这个问题,接下来我们介绍2种最常用的解决方案,更为详细的解决方案将放在另一篇章进行介绍。


1、内置锁synchronized


Java提供了一种内置锁机制来解决线程安全问题:synchronizedblock(同步代码块)。同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。


每个Java对象都可以用作一个实现同步的锁,线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。


Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。当线程1尝试获取一个由线程2持有的锁时,线程1必须等待或阻塞,直到线程2释放这把锁(如果线程2永远不释放锁,那么线程1将一直等下去)。


我们在此使用synchronized内置锁解决前面示例的线程安全问题,具体实现请参考以下实现代码。


public class UnsafeStates {


   private intstates =0;


   public synchronized int getStates() {

       states++;

       return states;

   }


   public static void main(String[] args) {


       final UnsafeStates states = new UnsafeStates();


        new Thread() {

           public voidrun() {

               for(int i = 0; i <1000000;i++) {

                    System.out.println(states.getStates());

               }

            }

        }.start();


        new Thread() {

           public voidrun() {

               for(int i = 0; i <1000000;i++) {

                    System.out.println(states.getStates());

               }

            }

        }.start();

   }

}


对getStates()方法添加synchronized内置锁后,线程间调用互不干扰,最后运行的结果的最后一个值为2000000,具体运行结果请参考图3。


java培训


3 添加synchronized内置锁后的运行结果


2、Lock


Lock是在Java 1.6被引进来的,跟synchronized内置锁相比,操作起来会比较复杂(Lock需要我们自己手动获取锁和释放锁,甚至可以中断获取以及超时获取同步特性)。还是以前面的示例为例,看看我们是如何使用Lock来保证线程安全的。


public class UnsafeStates {

   private intstates =0;

    private Lock lock =new ReentrantLock();


    public intgetStates() {

       lock.lock();

        try{

           states++;

       }catch (Exception e) {

            e.printStackTrace();

       }finally {

           lock.unlock();

       }

       

       return states;

   }


   public static void main(String[] args) {

       final UnsafeStates states = new UnsafeStates();


        new Thread() {

            public void run() {

               for(int i = 0; i <1000000;i++) {

                    System.out.println(states.getStates());

               }

            }

        }.start();


        new Thread() {

           public void run() {

               for(int i = 0; i <1000000;i++) {

                    System.out.println(states.getStates());

               }

            }

        }.start();

   }

}


进入方法后,首先要获取到锁,然后再执行业务代码。这里跟synchronized不同的是,Lock获取的锁对象需要我们亲自进行释放,为了防止我们代码出现异常,我们的释放锁操作放在finally中(finally中的代码无论如何都是会执行的)。我们通过多次实测数据,来验证下使用Lock是否能解决线程安全问题,具体运行结果如下。


9.png

图4 添加Lock锁后的运行结果 


根据运行的结果表示,使用Lock锁是可以解决线程安全问题的。其实Lock还有另外几种获取锁的方式,比如使用tryLock()方法获取锁。tryLock()方法跟Lock()方法是有区别的,Lock在获取锁的时候,如果拿不到锁,就一直处于等待状态,直到拿到锁,但是tryLock()却不是这样的,tryLock是有一个Boolean的返回值的,如果没有拿到锁,直接返回false,停止等待,它不会像Lock()那样去一直等待获取锁。关于tryLock()方法的使用,在这就不再一一进行介绍了,后面将通过另一篇章进行详细的介绍。


注释:本文来自微信公众号码农之屋

上一篇:【就业喜报】从百知毕业后,我月薪2万

下一篇:应届生去公司找个Java程序员的职位需要什么技能?

相关推荐

www.baizhiedu.com

有位老师想和您聊一聊

关闭

立即申请