在实习的过程中,发现实际项目中,非常多的地方用到了多线程,一旦用到了多线程,就会牵扯到线程的安全与否问题,如果对线程安全不了解,就会产生非常多的“奇奇怪怪”的问题,比如一个字段明明赋了值,为什么return的时候是个null,又或者是某个数据只是经过简单的计算,最终却得到一个不正确的数值。
定义
一个对象或方法能够被多个线程同时访问而不会导致任何问题的能力被称为线程安全。
在Java中,一个类的线程安全指的是当多个线程同时访问该类的实例或静态成员时,不会发生数据竞争或者其他并发问题。线程安全的类可以确保它的方法在多线程环境中能够正确地工作,而不需要额外的同步或协调。如果一个对象或方法是线程安全的,那么多个线程可以同时使用该对象或方法而不会导致任何数据损坏、死锁或其他并发问题。
一个类如果不是线程安全的,则在多个线程同时访问该类的实例或静态成员时,可能会发生数据竞争或其他并发问题,同时访问该对象或方法可能会导致数据损坏、死锁或其他并发问题,导致程序出现意外的行为。为了确保线程安全,需要在实现类的代码中使用同步机制,如synchronized关键字、Lock接口等。此外,还可以使用线程安全的数据结构和算法来确保类的线程安全。
示例
首先定义一个简单的线程不安全计数器
public class UnsafeCounter{
private int count;
public void increment(){
count++;
}
public int getCount(){
return this.count;
}
}
该计数器实现最简单的自增1的功能。
类似创建一个线程安全计数器,对increment()的方法进行synchronized
public class SafeCounter{
private int count;
public synchronized void increment(){
count++;
}
public int getCount(){
return this.count;
}
}
创建两个线程,同时调用increment()方法
public static void main(String[] args) {
UnsafeCounter UnsafeCounter = new UnsafeCounter();
SafeCounter safeCounter = new SafeCounter();
Runnable task = () -> {
for (int i = 0; i < 100000; i++) {
UnsafeCounter.increment();
safeCounter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("final unsafeCount:"+UnsafeCounter.getCount());
System.out.println("final safeCount:"+safeCounter.getCount());
}
两个线程都对计数器进行1000次increment操作
结果
第一次
final unsafeCount:1999
final safeCount:2000
第二次
final unsafeCount:1993
final safeCount:2000
第三次
final unsafeCount:1998
final safeCount:2000
可以看到,线程安全和不安全的区别是非常大的,如果数据需多线程操作,一定要考虑线程是否安全的问题,一旦出现数据不一致的问题上了生产影响是非常大的。