多线程无锁AtomicIntegerFieldUpdater<T>的使用出现的并发解释

多线程并发的解决分两类:加锁和无锁。

1、加锁:使用synchronized修饰或者使用jdk的并发工具类包进行加锁

      缺点:性能上稍微差点,可能会出现死锁。(当线程阻塞的时候,当抢夺资源失败到阻塞会有几万个时钟(硬件上的))

2、无锁:使用CAS 算法实现,---里面具体实现了unsafe的操作。实现原子操作

       优点:性能上更好,但是并不是很多场景都能用,有时候相对复杂。简单的还可以。



下面我们说一下AtomicIntegerFieldUpdater 更新器的使用。

AtomicIntegerFieldUpdater:原子更新整型的字段的更新器


package com.zpl.nolock;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * 尤其注意这个在没有system.out的情况下出现数据脏读。
 * 
 * @author zhangpengliang
 *
 */
public class AtomicIntegerFieldUpdaterTest {
	static class Student {
		private String name;
		public volatile int age;

		public Student(String name, int age) {
			super();
			this.name = name;
			this.age = age;
		}
	}

	/**
	 * 反射获取student类中的字段age,该字段必须是volatile修饰并且不能是private.并把这个student(age)作为共享
	 */
	private static AtomicIntegerFieldUpdater<Student> fu = AtomicIntegerFieldUpdater
			.newUpdater(Student.class, "age");

	public static void main(String[] args) {
		Student st = new Student("张三", 0);
		java.util.List<Thread> v = new ArrayList<Thread>();
		for (int i = 0; i < 5; i++) {
			ThreadA a = new ThreadA(st);
			Thread b = new Thread(a, "线程" + String.valueOf(i));
			v.add(b);
		}
		for (int i = 0; i < 5; i++) {
			Thread th = v.get(i);
			th.start();
		}

	}

	static class ThreadA implements Runnable {
		private Student st;

		public ThreadA(Student st) {
			super();
			this.st = st;
		}

		public void run() {
			// fu.getAndIncrement(st);
			// System.out.println("当前" + Thread.currentThread().getName()
			// + "执行新增之后的结果" + fu.get(st));
			// 或者
			int expect = fu.get(st);
			int value = expect + 1;
			System.out.println(fu.get(st));
			// 为什么加这一行就没有并发问题,不加就会有并发问题呢?原因是:在println里面加了一把锁。synchronized(this)
			// 如果不加system.out.println的话我们可以加一个锁,把下面的作为同步代码块。
			// 这里我们的compareAndSet是原子操作,但是与下面的打印并不是一体的,可能我们第一个线程增加完后,第二个线程直接过来了,而且他的expect值是改过的,这时候
			// 就又可以执行原子操作增加1了。导致我们第一次打印并没有打印出真正的第一次增加完后的值。

			/**
			 * 至于上面添加了一个System.out.println(expect),代码就可以实现同步的原因是:我们下面的system.
			 * out与上面那个system.out 在多个线程下执行时同步的。因为源代码中添加了锁(同步代码块)
			 */
			if (fu.compareAndSet(st, expect, value)) {
				System.out.println("当前" + Thread.currentThread().getName()
						+ "执行新增成功之后的结果" + fu.get(st));
			} else {
				System.out.println(
						"当前" + Thread.currentThread().getName() + "执行失败");
			}

		}
	}

}
上面代码我们需要注意的就是:Student类中的age 必须volatile修饰保证可见性,而且age不能是private修饰。

在操作中有的困惑:



这里如果不添加System.out.println().打印的结果并不是我们想要的。



但是最终的结果却仍是对的,五个线程,最总结果却不错,为什么打印的不对呢。

再看下加上system.out.println()..看下结果


这个结果却是我们想要的,为什么会出现脏读现象呢?

原因在于:当我们不加这个system.out.println时,由于compareAndSet是原子操作,不可拆分,但是。它与下面的打印信息并不是。当所有线程 2 4 0 1 3 这个五个线程打印的时候其实所有线程的compareAndSet已经执行操作完 了,当打印的时候,

有这种可能:线程0打印的时候已经执行加到4了,线程1 2 3也是.当4打印的时候值已经是5了。

但是在打印的时候又阻塞了,因为:


因为打印是同步代码块。所以阻塞了,所以打印就需要抢占当前资源。

为啥加了哪行打印就能看到了呢?

原因还是这个,所有的打印都是抢占的一个资源。我们看打印的日志:

0
1
当前线程4执行新增成功之后的结果2
当前线程1执行新增成功之后的结果1
0
当前线程0执行失败
0
当前线程2执行失败
0
当前线程3执行失败
只有拿到这打印资源后,才可以执行后面的compareAndSet加1.

但我觉得最安全的做法就是在if条件外加一个synchronized修改(同步代码块)。并能按我们设置的输出





至少结果是一部一部来的。这里只是解释了下为什么会有这个现象。就当做个备注学习下了。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值