第一课 内存可见性问题(volatile关键字)

  • Java在1.5之后的版本中加入了JUC(java.util.concurrent包)
  • 内存可见性问题:当多个线程操作共享数据时,彼此不可见
  • JVM会为每一个线程分配独立的缓存用于提高效率

1 内存可见性问题逐步解决(一个线程读,一个线程写)

1.1 原始问题(内存可见性问题现象)

  • 原始程序的问题:运行过程中,Thread-0线程进行flag值的更改,main线程用于访问flag的值,由于while循环更加接近底层代码,因此运行效率极高,最终会导致无法从ThreadDemo中获取已经被Thread-0线程更改过的新的flag值,此时两个线程是不可见的,while循环变成了死循环。

  • 原始代码执行过程

原始代码执行过程

package JUC.Volatile;

/**
 * 以下为原始程序:在main方法中共有两个线程,首先是demo线程,其次是main线程
 * 
 * @author Yorick
 *
 */
public class VolatileTest {
	public static void main(String[] args) {
		// Thread-0线程
		ThreadDemo demo = new ThreadDemo();
		new Thread(demo).start();
		// main线程
		while (true) {
			if (demo.isFlag()) {
				System.out.println(Thread.currentThread().getName() + "----------------");
				break;
			}
		}
	}
}

class ThreadDemo implements Runnable {
	private boolean flag;

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.flag = true;
		System.out.println(Thread.currentThread().getName() + "isflag" + ":" + isFlag());
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

}

1.2 通过加锁的方式解决线程可见性问题

  • 效率低下
public static void main(String[] args) {
	// Thread-0线程
	ThreadDemo demo = new ThreadDemo();
	new Thread(demo).start();
	// main线程
	while (true) {
		// 通过加锁的方式,每次循环都会重新从ThreadDemo中获取新的flag值
		synchronized (demo) {
			if (demo.isFlag()) {
				System.out.println(Thread.currentThread().getName() + "----------------");
				break;
			}
		}
	}
}

1.3 通过volatile关键字解决线程可见性问题

  • volatile关键字作用:当多个线程进行操作共享数据时,保证内存中的数据可见(底层采用了内存栅栏),相较于synchronized是一种较为轻量级的同步策略
  • volatile使用注意:
    • 不具备互斥性
    • 不嫩敢保证变量的原子性
class ThreadDemo implements Runnable {
	private volatile boolean flag;

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.flag = true;
		System.out.println(Thread.currentThread().getName() + "isflag" + ":" + isFlag());
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

}

2 原子性问题(两个线程均存在读写操作)

2.1 原始问题(原子性问题现象)

  • 产生原因

原子性问题原始代码执行过程

package JUC;

public class AtomicTest {
	public static void main(String[] args) {
		ThreadDemo2 demo2 = new ThreadDemo2();
		for (int i = 0; i < 10; i++) {
			new Thread(demo2).start();
		}
	}
}

class ThreadDemo2 implements Runnable {
	private int i = 0;

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + i++);
	}
}

// 运行结果:每个线程单独对i变量进行自增操作,不应该出现两次0,存在线程安全问题
//Thread-1:0
//Thread-4:3
//Thread-2:1
//Thread-3:2
//Thread-0:0
//Thread-5:4
//Thread-6:6
//Thread-7:5
//Thread-8:8
//Thread-9:7

2.2 原子变量

  • jdk1.5之后java.util.concurrent.atomic包下提供了常用的原子变量,该变量使用了如下的技术
    • 使用volatile保证内存可见性
    • 使用CAS(Compare-and-Swap)算法保证数据的原子性,CAS算法是硬件对于并发操作共享数据的支持,包含了:内存值(V),预估值(A)和更新值(B)。当且仅当V等于A时,才会把B的值赋值给V,否则不会进行任何操作。
    • 通过使用以上特性,可以保证每次有且只有一个线程能对共享数据操作成功。没有成功的线程不会被挂起,而是再次尝试更新操作,因此效率会高于加锁的方式
class ThreadDemo3 implements Runnable {
	// 原子变量(从10开始追加)
	private AtomicInteger atomicInteger = new AtomicInteger(10);

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 先获取值再自增,相当于i++
		System.out.println(Thread.currentThread().getName() + ":" + atomicInteger.getAndIncrement());
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值