Java 多线程系列Ⅱ

2024-01-30 08:55:10 浏览数 (2)

一、引言

在多线程环境中,线程安全是确保程序正确性的关键因素。Java作为一种广泛使用的编程语言,其线程安全的概念、策略和案例分析对于开发人员来说至关重要。

二、Java线程安全概念

  1. 线程安全定义

线程安全是多线程编程中的重要概念,它指的是在并发环境中,共享数据的一致性和完整性得到保证。换句话说,在多线程环境中,线程安全能够防止数据竞争和不可预测的行为。

  1. Java中的线程安全

在Java中,线程安全性主要通过synchronized关键字volatile关键字原子类以及来实现。这些机制可以确保在多线程环境下,对共享资源的访问是互斥的,从而避免数据竞争和不一致性问题。

  1. Java中线程不安全的情况
  • 共享变量:在多线程环境中,如果多个线程同时访问和修改同一个共享变量,就可能导致数据不一致的问题。例如,两个线程同时对一个计数器进行加1操作,由于操作顺序不确定,最后得到的结果可能不是期望的结果。
  • 非同步方法:如果一个方法没有进行同步处理,那么当多个线程同时调用该方法时,就可能出现数据竞争的问题。例如,一个线程正在执行一个方法,另一个线程突然插入了该方法的中间代码,就可能导致第一个线程得到错误的结果。
  • 死锁:死锁是指两个或多个线程互相等待对方释放资源,导致程序无法继续执行的情况。例如,线程A拥有资源1,线程B拥有资源2,两个线程都想要对方手里的资源,但是谁都不愿意先释放自己的资源,结果就形成了死锁。
  • 线程间通信问题:线程间通信也可能导致线程不安全的问题。例如,一个线程正在等待某个条件成立(如另一个线程已经完成了某个任务),而另一个线程迟迟不满足该条件,就会导致第一个线程一直等待下去,浪费CPU资源。
  • 资源竞争:资源竞争是指多个线程同时争夺同一资源,导致某些线程无法获得足够的资源而出现异常的情况。例如,多个线程同时访问同一个文件或数据库连接,就有可能导致某些线程无法获取到所需的资源而抛出异常。

三、Java线程安全策略

  1. 使用synchronized关键字

synchronized关键字是Java提供的一种内置的线程同步机制。它可以应用于方法或代码块,确保同一时刻只有一个线程可以执行该代码块。例如:

代码语言:javascript复制
public synchronized void add(int value) { 
    this.count  = value; 
}
  1. 使用volatile关键字

volatile关键字用于确保多线程对共享变量的访问是原子的。当一个变量被声明为volatile时,它会保证修改的值会立即被更新到主内存中,从而避免线程之间的数据不一致。例如:

代码语言:javascript复制
public class Counter { 
    private volatile int count; 
    //... 
}
  1. 使用原子类

Java提供了原子类(如AtomicInteger、AtomicLong等),这些类提供了更精确的线程安全操作。它们使用内部锁或CAS(Compare-and-Swap)操作来确保对共享资源的访问是原子的。例如:

代码语言:javascript复制
public class Counter { 
    private AtomicInteger count = new AtomicInteger(0); 
    // ... 
}
  1. 使用显式锁

Java提供了显式锁(如ReentrantLock、ReadWriteLock等),允许开发人员更灵活地控制线程同步。这些锁可以确保对共享资源的访问是互斥的,从而避免数据竞争。例如:

代码语言:javascript复制
public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count  ;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

四、具体例子

案例1:我们有一个简单的程序,其中有一个计数器变量count,两个线程分别对其进行加1操作。由于没有进行同步处理,结果可能会出现数据不一致的情况。

代码语言:javascript复制
public class Counter {  
    private int count = 0;  
  
    public void increment() {  
        count  ;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}  
  
public class ThreadA extends Thread {  
    private Counter counter;  
  
    public ThreadA(Counter counter) {  
        this.counter = counter;  
    }  
  
    @Override  
    public void run() {  
        for (int i = 0; i < 10000; i  ) {  
            counter.increment();  
        }  
    }  
}  
  
public class ThreadB extends Thread {  
    private Counter counter;  
  
    public ThreadB(Counter counter) {  
        this.counter = counter;  
    }  
  
    @Override  
    public void run() {  
        for (int i = 0; i < 10000; i  ) {  
            counter.increment();  
        }  
    }  
}

案例分析:为什么会出现数据不一致的情况?

上图存在一种情况就是,线程A线程B如果几乎同时读取 i = 0 到自己的工作内存中。

线程A执行 i  结果后将 i = 1 赋值给工作内存;但是这个时候还没来的将最新的结果刷新回主内存的时候线程B读取主内存的旧值 i = 0 ,然后执行use指令将 i = 0的值传递给线程B去进行操作了。

即使这个时候线程A立即将 i = 1刷入主内存那也晚了线程B已经使用旧值 i = 0进行操作了,像这种情况计算结果就不对了。

解决方案: 可以使用synchronized关键字对increment()方法进行同步处理,以确保同一时刻只有一个线程可以访问该方法。这样就可以避免数据竞争和数据不一致的问题。

代码语言:javascript复制
public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count  ;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

案例2:我们有一个程序,其中有两个线程A和B,它们需要共享一个布尔型变量flag。线程A负责将flag设置为true,线程B负责在flag为true时执行一些操作。

代码语言:javascript复制
public class FlagExample {  
    private volatile boolean flag = false;  
  
    public class ThreadA extends Thread {  
        @Override  
        public void run() {  
            flag = true;  
        }  
    }  
  
    public class ThreadB extends Thread {  
        @Override  
        public void run() {  
            if (flag) {  
                // 执行一些操作  
            }  
        }  
    }  
}

案例分析:由于没有进行同步处理,可能会出现线程B已经读取了flag的旧值(false),而在线程A还没有更新flag之前,线程B就执行了操作的情况。这样就会导致线程B执行了不必要的操作。

解决方案:可以使用synchronized关键字对setFlag()方法和flag变量进行同步处理,以确保同一时刻只有一个线程可以访问该方法和变量。这样就可以避免线程B执行了不必要的操作的问题。

代码语言:javascript复制
public class FlagExample {  
    private volatile boolean flag = false;  
    private final Object lock = new Object();  
  
    public class ThreadA extends Thread {  
        @Override  
        public void run() {  
            synchronized (lock) {  
                flag = true;  
            }  
        }  
    }  
  
    public class ThreadB extends Thread {  
        @Override  
        public void run() {  
            synchronized (lock) {  
                if (flag) {  
                    // 执行一些操作  
                }  
            }  
        }  
    }  
}

0 人点赞