文章目录
线程的三大特性
原子性
即一个操作或多个操作要么全部执行并且执行过程中不被任何因素打断,要么就不执行。
package com.wdy.thread;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadAtomicDemo {
public static int i=0;
public static class AtomicThread extends Thread{ //静态内部类继承Thread
@Override
public void run() {
while(true) {
i++;//i++,i--,++i,--i 不是原子性操作。
//比如i++ 先赋值,后自增两个步骤,中间有间隔。这个两个步骤可能出现在两个线程中。因此结果中出现了两个连续重复的i
//因此这样的操作是线程不安全的。
System.out.println(this.getName()+"::"+i);
try {
sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1=new AtomicThread();
t1.setName("A");
Thread t2=new AtomicThread();
t2.setName("B");
Thread t3=new AtomicThread();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
i++,i–,++i,–i 不是原子性操作。比如i++ 先赋值,后自增两个步骤,中间有间隔。这个两个步骤可能出现在两个线程中。因此结果中出现了两个连续重复的i。因此这样的操作是线程不安全的。执行结果:
使用AtomicInteger类创建原子性的对象,i.incrementAndGet()方法进行自增,保证原子性,线程安全。
CAS
通过不断检查线程中的值是否被改变过,这个过程叫自旋。自旋就是CAS的一个操作周期。如果值已经改变,那么就等下下一个自增值再更新值。模式图如下:
ABA问题:
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A, 变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这样就线程不安全了。
0–>0(v1.1)–>0+1–>是否为0–>1(v1.1) 版本控制
当出现了ABA问题,CAS通过定义版本号的方法来确认数据是否已经被改动。底层代码如下。
CAS存在的一些其它问题:
1.自旋如果长时间不成功,会给CPU带来非常大的执行开销。
2.只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
package com.wdy.thread;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadAtomicDemo {
public static int i=0;
public final static AtomicInteger i=new AtomicInteger(0);
public static class AtomicThread extends Thread{ //静态内部类继承Thread
@Override
public void run() {
while(true) {
i++;
System.out.println(this.getName()+"::"+i.incrementAndGet());
try {
sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1=new AtomicThread();
t1.setName("A");
Thread t2=new AtomicThread();
t2.setName("B");
Thread t3=new AtomicThread();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
执行结果如下:
可见性
当多个线程同时访问一个变量时,一个线程修改了这个变量的值,其它线程能立即看得到它修改的值。
volatile关键字
volatile关键字就是用于保证内存可见性,当线程A更新了volatile修饰的变量时,它会立即刷新到主内存中,并且将其余缓存中该变量的值清空,导
致其余线程只能去主内存读取最新值。
使用volatile关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。
package com.wdy.thread;
public class ViasbleDemo01 {
public static volatile boolean f=false;
public static class A extends Thread{
@Override
public void run() { //如果f=true,执行循环
while(true) {
if(f) {
System.out.println(getName()+"运行:"+f);
break;
}
}
}
}
public static class B extends Thread{
@Override
public void run() {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
f=true;
System.out.println(getName()+"运行"+f);
}
}
public static void main(String[] args) {
Thread t1=new A();
t1.setName("A");
Thread t2=new B();
t2.setName("B");
t1.start();
t2.start();
}
}
初始状态,f 变量没有加volatile(可见性)。A线程f=false,不执行。B线程执行结果:f=true。
此时f变为true。那么A线程应该也会打印,但从结果来看,并没有执行A。这是因为线程之间是不可见的。虽然B线程把f变为true,但对A线程来说是不可见的。
那么如果要让B线程可见,只需在全局变量 f 前面加上 volatile(可见性)。此时,B线程结果可见。执行结果如下:
A、B线程打印顺序和sleep时间有关。把A的sleep改成10,B线程睡眠时间足够短,那就是B先打印。结果如下:
有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在多个线程运行时,不要调整内部指令执行的顺序。
package com.wdy.thread;
/*
* 单例模型
*/
public class OrderThreadDemo {
private static volatile OrderThreadDemo instance = null;
//添加volatile的作用;解决可变性,保证应用的有序性(按第一种方式),
private OrderThreadDemo() {}
public static OrderThreadDemo getInstance() {
/**
*假设有两个线程进入方法
*if instance == null
* 准备创建对象 instance = new OrderThreadDemo();
*可能分为三个流程:第一种方式
*1.开辟内存空间
*2.初始化对象
*3.引用和对象绑定
*
*
另外一种方式:
*1.开辟内存空间
*2.引用和对象绑定instance--->对象, instance不是null
*3.初始化对象(未完成)
*/
if(instance==null) { //判断OrderThreadDemo是否存在
instance=new OrderThreadDemo();
}
return instance;
}
}