线程安全与线程不安全

来源:互联网 发布:数据库置疑脱机怎么办 编辑:程序博客网 时间:2024/05/04 18:56

什么是线程安全?

比较官方的解释如下:

当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的。

上面的解释你可能看起来会懵,但没关系,接下来用代码代码来解释下什么是线程安全。

先说线程不安全

package thread;import java.util.concurrent.CountDownLatch;/** *  * @author wiggin * 线程不安全类 * */public class Unsafe {private static int num;//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {num++;}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}

上面面的程序启动十个线程,每个线程对数字num进行1000次累加,理想状态下累加完成后最终num为10000,但是你可以试着跑多次,你会发现每次出现的结果都不一样。

之所以会出现这样的情况,是因为++操作并不是原子操作,虽然它看起来只有一行,而且非常紧凑,但是他包含了三个操作:

①   读取num的值

②   将值+1

③   计算结果赋值给num

从代码生成的字节码序列可看出上面的三步操作

public static void increate();

   Code:

      0: getstatic     #2    获取指定类的静态域,并将其押入栈顶

      3: iconst_1                 将int型1推送至栈顶

      4: iadd                       将栈顶两int整形值相加,并将结果押入栈顶

      5: putstatic     #2   为指定的类的静态域赋值

      8: return                    返回  


多线程操作的时候,就可能出现同一时刻多个线程读取到同样的num值,之后进行累加,这就会导致结果的不准确。大概流程如下所示:



怎么能让它变成线程安全的呢?

有人可能想到将num用volatile去修饰变量,然而遗憾的是,volatile值保证可见性,但是并不保证原子性。也就是说:当前线程修改num值之后,对其他线程立即可见,但依旧没法保证num++是一个原子性的操作。也就还是同样会出现上面那样的情况。

正确的方法有:

①   使用同步synchronized关键字

package thread;import java.util.concurrent.CountDownLatch;/** *  * @author wiggin * 线程安全类 * */public class Safe {private static int num;//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {synchronized(countDownLatch){num ++;}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}

②   将num修改为AtomicInteger变量

package thread;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;/** *  * @author wiggin * 线程安全类 * */public class Safe {//将变量改为AtomicIntegerprivate static AtomicInteger num = new AtomicInteger(0);//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {num.incrementAndGet();}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}

③   使用锁

package thread;import java.util.concurrent.CountDownLatch;import java.util.concurrent.locks.ReentrantLock;/** *  * @author wiggin * 线程安全类 * */public class Safe {private static int num;//使用锁private static ReentrantLock reentrantLock = new ReentrantLock();//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static  void increate () {//使用reentrantLock必须显式释放锁try {reentrantLock.lock();num ++;} finally {reentrantLock.unlock();}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}






原创粉丝点击