JAVA学习之JUC学习(一)
首先我们要知道什么时候才会出现java并发问题,简单点来说就是多个线程同时去处理一个公用的数据是会出现java的并发问题,如当我们启动多个线程去修改同一个数据时,就会出现这样的问题。
首先我们应该知道作为JUC编程的几个基本特性
一,可见性
废话不多说直接上代码看问题
public class VolatileTestDemo {
public static void main(String[] args) {
Change c=new Change();
new Thread(c).start();
while(true){
if(c.isFlag()){
System.out.println("准备跳出循环");
break;
}
}
}
}
class Change implements Runnable{
private boolean flag=false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag=true;
System.out.println("-----------");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
在上面的代码中我们可以看到我在线程中已将将flag设置成true但是while(true)的循环中还是没有跳出循环,为什么呢?是因为线程在启动的时候就已将将全局变量即private boolean flag=false已经复制了一份副本在本线程中,他不会在从主存中读取数据,也就是说我们在线程中修改的flag=true,其实在正在运行的线程中没有生效,主存中的变量其实已经为true,但是线程没有将原来读取到的数据进行更新,想要解决此问题我们只需要再读取一次主存中的数据即可解决,这时候我们就需要用到volatile关键字来将读取到的副本进行刷新。我们只需要将flag字段用volatile进行修饰,就可以解决上述问题。
二,原子性
我们发现在解决上述问题以后,当我们在对一个数据进行并发修改的时候会出现新的问题。依旧是直接上代码。
public class AtomicTestDemo {
public static void main(String[] args) {
AtomicTest at =new AtomicTest();
for (int i = 0; i < 10; i++) {
new Thread(at).start();
}
}
}
class AtomicTest implements Runnable{
private int commonNum=0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println("当前线程"+Thread.currentThread().getName()+"获取当前公共区域的数据结果:"+getCommonNum());
}
private int getCommonNum(){
return commonNum++;
}
}
运行结果为
当前线程Thread-2获取当前公共区域的数据结果:0
当前线程Thread-5获取当前公共区域的数据结果:5
当前线程Thread-3获取当前公共区域的数据结果:4
当前线程Thread-1获取当前公共区域的数据结果:3
当前线程Thread-0获取当前公共区域的数据结果:2
当前线程Thread-4获取当前公共区域的数据结果:1
当前线程Thread-6获取当前公共区域的数据结果:0
当前线程Thread-7获取当前公共区域的数据结果:6
当前线程Thread-8获取当前公共区域的数据结果:7
当前线程Thread-9获取当前公共区域的数据结果:8
我们发现出现了两个0,为什么会出现这个问题,就是在线程2获取到当前的值为0,在进行++操作之前,线程6拿到当前的CommonNum为0,也进行操作这是就出现上面的问题当然出现上述问题的时候我们可以使用java.util.concurrent.atomic包下的类型行进行定义
我们可以通过提供的类型类进行我们需要的相关操作,文档中的这些类是如何实现它的原子性的是通过一种CAS算法来进行实现的,CAS是基于硬件的算法,下一次我们来模拟CAS算法是如何实现其原子性的。