Java面试中线程安全的相关问题(一)_

在面试过程中,你可能会被问到关于线程安全的问题。这里我们讲一下多线程写文件有线程安全问题,主要包括两个方面。

线程安全的相关概念以及线程安全如何在内存中体现。

首先让我们看一下线程安全的概念:

通过这段代码可以直观的看到线程安全和线程不安全的表现。这段代码比较长,我们一步一步看:

在第9行的run方法中,我们定义了线程的主代码;

在它的第 13 行中,我们可以看到在每个线程中我们将元素添加到集合中;

在第 20 行的 for 循环中,我们启动了 10,000 个线程,每个线程向集合中添加元素;

在第 31 行的 main 方法中,我们创建了两个集合,其中第 33 行的 unsafeList 是线程不安全的;

第 34 行的 unsafeList 是线程安全的;

在第 36 行,我们运行了 3 次 for 循环,每次将 10,000 个元素插入到线程安全和不安全对象中;

从运行结果可以看出,线程安全对象每次运行的元素个数为10000。原因是它不会被抢占,线程不安全的对象可能因为这种现象而被抢占,所以我们说它里面的元素个数会小于10000,如果你运行这段代码就会看到抢占。

图片[1]-Java面试中线程安全的相关问题(一)_-老王博客

我们从内存的角度来分析一下抢占的原因。从内存的角度来看,如果每个线程都想对数据变量进行操作,那么这个线程会先将变量加载到(线程)内存中进行复制,操作完成后再继续。回信,请注意这里有主存和线程内存两种。从这张图可以看出,如果两个线程在读取和操作的时候是并发的多线程写文件有线程安全问题,那么就可以看到抢占现象,这就解释了刚才抢占的原因。

面试的时候,除了说明原因,还需要解决实际问题,就是要告诉面试官你在哪些场景下选择了线程安全对象和线程不安全对象。

首先,你必须说哪些对象是线程不安全的,哪些是安全的。其中,我们列出了一些线程安全的对象,如 Vector、HashTable 和 StringBuffer。对应的线程不安全对象是 LinKedlist、Arraylist、HashMap 和 StringBuilder。

其次,你可以向面试官表达这个意思,因为线程安全的对象需要消耗成本,而我们的运行环境大部分是单线程环境,所以如果没有特殊原因,可以选择线程不安全的对象,因为这个性能的高,除非有特殊要求,比如这种场景下的多线程并发,而且我需要保证数据的准确性,那么我可以选择线程安全的对象。如果我们在某些场景下需要使用ArrayList,但同时又需要保证线程安全,那么我们可以使用Collections.synchronizedList方法来封装它,而对于Map我们可以使用Collections.synchronizedMap方法来封装它。

刚才我们谈到了线程安全的话题。由于 Volatile 关键字与线程安全关系非常密切,所以在面试过程中很有可能会被问到这个关键字。我们一起来解释一下。

如果在变量前加上Volatile,线程每次使用都会从主存中读取,一旦修改,修改后会立即写回主存,所以混淆就在这里。既然会立即读写,那岂不是解决了线程安全问题?也就是说,这个关键字能保证原子性吗?不,我们来看看这段代码。在这段代码中,我们在cnt变量之前添加了Volatile,并在add方法中进行了++操作。在第 10 行的 main 方法中,我们通过 for 循环启动了 1000 个线程同时进行加法操作。最后,让我们看看它的价值。如果 Volatile 关键字可以保证线程安全,那么它的值应该是 1000,因为它不会被抢占,但是从运行结果来看,这个结果会小于 1000。

换句话说,虽然 Volatile 关键字说它可以立即读写,但它不能保证原子性,这意味着 Volatile 关键字不能保证线程安全。

那么 Volatile 关键字有什么作用呢?从我们刚才的描述可以看出,Volatile 可以减少从主存读取和回写的步骤,这意味着它可以提高性能。

尤其是在某些场景下,比如我们只读取这个变量或者只写入这个变量,那么就可以使用 Volatile 来提高性能。

我们在这里再次强调,这个关键字不保证原子性,也不保证线程安全。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论