本文共 3230 字,大约阅读时间需要 10 分钟。
在实际开发中,尤其是在多线程环境下,数据操作的 safety 至关重要。 本文将从reead a. y的小示例出发, 分析线程不安全现象,并探讨两种解决方案:使用 synchronized 锁 和 ConcurrentLinkedQueue 实现的无锁化机制。
在典型的多线程应用中,尤其是当多个线程竞争访问同一资源时,线程安全问题往往会引发诸多麻烦。 例如,在本文中的示例中,一个 LinkedList
被多个线程同时操作, 每个线程都尝试将唯一的、随机字符串添加到列表中。
然而,由于线程竞争, 某些线程可能会抢占到同一地址, 导致资源未能正常进行操作, 甚至导致数据不一致或其他不可预测的现象。 在本文的示例中, 线 hastily 变多, 现象就更加明显。
为了解决上述问题,我们可以采用最传统的方式——使用 synchronized 锁。这是一种 monopolistic机制, 可以保证在任意时刻, 只有单一个线程能够访问受锁定保护的资源。
在改进后的代码中,我们采用了:
import java.util.Collections;import java.util.LinkedList;import java.util.UUID;public class LinkedListThread { public static void main(String[] args) { final LinkedList linkedList = new LinkedList(); Collection ts = Collections.synchronizedCollection(linkedList); for (int i = 0; i < 4; i++) { new Thread(new Runnable() { @Override public void run() { linkedList.add(UUID.randomUUID().toString().substring(0, 10)); System.out.println(linkedList); } }).start(); } }}
这样,每个 Runnable
实例都将获得同一 synchronizedCollection
锁。 当一个线程开始运行, 它会先获取锁定权限, 完成操作后,再释放锁定。在此期间, 其他线程将被阻塞, 保证了操作的安全性。
在这次改进的示例中, 我们可以观察到以下输出结果:
[Thread-1] added string:[ Thread-1 adds "14e1555c-888a-4a0e-b4c2-3853f03c8b69", list size is 1 ][Thread-2] added string:[ Thread-2 adds "2a5be55c-6f3a-47f0-a8ef-4d62ab466e71", list size is 2 ][Thread-3] added string:[ Thread-3 adds "8d0c55d1-1a0f-4b94-b5c1-be5edc77f988", list size is 3 ][Thread-4] added string:[ Thread-4 adds "f26bf755-adad-4d8b-8fe0-32d3ef0a0aa", list size is 4 ]
可以看出,各 thread 都能够安全地访问和修改列表, 而且 each thread 都能完整地执行自己的任务, 而不再出现运算结果的不一致或内存损坏等现象。
若要实现更高效的线程安全, 我们可以考虑使用 ConcurrentLinkedQueue
。 这种队列采用无锁化机制(CAS - Compare And Swap)实现, 无需额外同步机制, 性能更高。 同时,我们也可以在此基础上, 使用 volatile
关键字, 提升内存本地性。
以下是改进后的代码示例:
import java.util.Collections;import java.util.ConcurrentLinkedQueue;import java.util.UUID;public class LinkedListThread { public static void main(String[] args) { final ConcurrentLinkedQueuequeue = new ConcurrentLinkedQueue<>(); for (int i = 0; i < 4; i++) { new Thread(new Runnable() { @Override public void run() { queue.add(UUID.randomUUID().toString().substring(0, 10)); System.out.println(queue); } }).start(); } }}
在这个实现中,我们使用了 ConcurrentLinkedQueue
, 这是一个专门为多线程环境设计的队列结构, 支持无锁化操作。 volatile
关键字则确保了这个队列被所有线程可见,从而保证了操作的一致性。
我们可以通过运行这个代码, 大家会看到:
[Thread-1] added string:[ Thread-1 adds "14e1555c-888a-4a0e-b4c2-3853f03c8b69", queue size is 1 ][Thread-2] added string:[ Thread-2 adds "2a5be55c-6f3a-47f0-a8ef-4d62ab466e71", queue size is 2 ][Thread-3] added string:[ Thread-3 adds "8d0c55d1-1a0f-4b94-b5c1-be5edc77f988", queue size is 3 ][Thread-4] added string:[ Thread-4 adds "f26bf755-adad-4d8b-8fe0-32d3ef0a0aa", queue size is 4 ]
可以看出, 四个 thread 都能安全地添加自己的元素,每个 thread 都有自己的独立访问, 并且最终的结果是确定的, 而不会出现线程不安全的问题。 这种实现同样能够带来比较好的性能, 因为减少了大规模的并发竞争, 而只是基于轻量级的原子操作(CAS)。
综上所述,在多线程环境下, 线程安全问题是一个需要认真对待的课题。 无论选择传统的 synchronized 锁, 还是现代的无锁化机制, 都有其适用的场景和优势。 通过合理的选择和优化,可以在保证线程安全的同时, 实现高效的数据操作。
转载地址:http://jkwfk.baihongyu.com/