kotlin小悟-安全调用符

Posted 写代码的林克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kotlin小悟-安全调用符相关的知识,希望对你有一定的参考价值。

今天看看kotlin中的安全调用符的一个注意点。

之前的文章已经讲过kotlin中的安全调用符,可以点击查看。

知识点

kotlin中的安全调用符 ?. 是线程安全的。

代码验证

我们打开IDEA写下面一段代码:

class Sample(var name: String?)
    fun test()
        if(name != null)
            println(name.length)
        
    

然后IDEA会提示你如下一段话:

简单一点说,IDEA认为name属性可能会在你判断完成之后,在你正式使用该变量之前被修改,也就是不是线程安全的。
想想也是,假如有一个Sample对象,被两个线程读取和修改,在不加锁的情况下,即使你if判断是非空的,但是等你正式使用的时候,可能已经被其他线程修改为null,这时候就会得到不期望的结果。

由于存在编译错误,无法在kotlin中演示上面的问题,我们在Java里面通过下面的代码演示一下:

public class Demo 
    private String name;

    public static void main(String[] args) throws InterruptedException 
        Demo demo = new Demo();
        demo.name = "name";
        Thread thread1 = new Thread(() -> demo.name = null);
        Thread thread2 = new Thread(() -> 
            if (demo.name != null) 
                try 
                    Thread.sleep(700);
                    System.out.println(demo.name.length());
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            else 
                System.out.println("deme.name is null");
            
        );

        thread2.start();
        Thread.sleep(500);
        thread1.start();
        Thread.sleep(1000);
    

运行上面的代码,尽管已经通过了if判断,还是会抛出空指针异常,其实这就是简单的线程不安全的例子。

那kotlin的安全调用运算符是如何解决线程安全问题的呢?

我们可以尝试写一段最简单的代码,然后通过IDEA查看字节码,然后再从字节码反编译回Java代码进行查看。
我们先写下面一段代码:

class Sample(var name: String?) 
    fun info() 
        println("name len = " + name?.length)
    

我们通过点击工具栏的 Tools–>Kotlin–>Show Kotlin Bytecode 会在右侧打开一栏展示Kotlin的字节码,然后我们点击 Decompile 按钮就可以将字节码转为Java代码了,下面是转化后的Java代码:

import kotlin.Metadata;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = 1, 1, 15,
   bv = 1, 0, 3,
   k = 1,
   d1 = "\\u0000\\u0016\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0002\\b\\u0005\\n\\u0002\\u0010\\u0002\\u0018\\u00002\\u00020\\u0001B\\u000f\\u0012\\b\\u0010\\u0002\\u001a\\u0004\\u0018\\u00010\\u0003¢\\u0006\\u0002\\u0010\\u0004J\\u0006\\u0010\\b\\u001a\\u00020\\tR\\u001c\\u0010\\u0002\\u001a\\u0004\\u0018\\u00010\\u0003X\\u0086\\u000e¢\\u0006\\u000e\\n\\u0000\\u001a\\u0004\\b\\u0005\\u0010\\u0006\\"\\u0004\\b\\u0007\\u0010\\u0004¨\\u0006\\u0000",
   d2 = "LSample;", "", "name", "", "(Ljava/lang/String;)V", "getName", "()Ljava/lang/String;", "setName", "info", ""
)
public final class Sample 
   @Nullable
   private String name;

   public final void info() 
      StringBuilder var10000 = (new StringBuilder()).append("name len = ");
      String var10001 = this.name;
      String var1 = var10000.append(var10001 != null ? var10001.length() : null).toString();
      boolean var2 = false;
      System.out.println(var1);
   

   @Nullable
   public final String getName() 
      return this.name;
   

   public final void setName(@Nullable String var1) 
      this.name = var1;
   

   public Sample(@Nullable String name) 
      this.name = name;
   

其实,我们只需要关注上面的 info() 函数,因为这里面使用了安全调用符,其实我们只需要关注下面两行即可:

String var10001 = this.name;
String var1 = var10000.append(var10001 != null ? var10001.length() : null).toString();

kotlin将我们的成员变量赋值给了var10001这个变量,后续的判断和使用都是去使用这个变量。
这样即使你其他线程修改了name属性的值,也就是将name这个引用指向了其他变量,也不会影响到var10001这个变量,var10001是个局部变量,在每个线程中都有一份自己的存在,是不共享的,自然就是线程安全的了。

顺便说一句,你会发现kotlin中下面的代码是不会报错:

fun sample(str: String?)
    if(str != null)
        println(str.length)
    

就是因为局部变量是线程安全的,只有上面演示的成员变量使用if简单进行判断是线程不安全的。

写在最后

神秘的东西,揭开面纱以后,也就没那么神秘了。

以上是关于kotlin小悟-安全调用符的主要内容,如果未能解决你的问题,请参考以下文章

kotlin小悟-这个继承有点不一样

kotlin小悟-这个继承有点不一样

Kotlin 符号( ‘?.‘ ‘?:‘ ‘!!‘ ‘as?‘ ‘?‘ )

Kotlin 符号( ‘?.‘ ‘?:‘ ‘!!‘ ‘as?‘ ‘?‘ )

Kotlin 符号( ‘?.‘ ‘?:‘ ‘!!‘ ‘as?‘ ‘?‘ )

Kotlin初级- - - 空安全.md