多线程特性
原子性
所谓原子性即:一个或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在原子操作中,本质上拒绝多线程操作的,不论是单核或多核服务器,当要对某一个数据进行原子操作时,同一时刻只有有一个线程能够对其进行操作,简单来说,在整个操作过程中不会被线程调度器打断,如a=1就是一个原子操作,但a++则不是一个原子操作,因为其内部会额外产生一个新的Integer对象。
举个例子,假设对一个32位的变量赋值,操作分为两步:低16位赋值、高16位赋值。当线程A对低16位数据写入成功后,线程A被中断。而此时另外的线程B去读取a的值,那么读取到的就是错误的数据。
在Java中的原子性操作包括:
基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操作。
所有引用的赋值操作。
java.concurrent.Atomic.* 包中所有原子操作类的一切操作。
测试
public class test {
public static void main(String[] args) throws InterruptedException {
MyInt myInt = new MyInt();
for(int i=0;i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"----->"+myInt.getNum());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
static class MyInt{
int num;
public int getNum(){
return num++;
}
}
}
可见性
所谓可见性:即当多个线程访问同一个共享变量时,一个线程修改了该共享变量的值后,其他线程能够立即查看到修改后的值。
在多线程环境下,一个线程对共享变量的操作对其他线程是默认是不可见的,也就是说一个线程对某一共享的修改,默认其他线程是无法进行查看的。而如果要做到可见,Java中的volatile、synchronized、Lock都能保证可见性。如一个变量被volatile修饰后,表示当一个线程修改共享变量后,其会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。而synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
有序性
所谓有序性:即程序执行的顺序会按照代码的先后顺序执行。
其可以理解为在本线程内,所有的操作都是有序的。而如果在A线程中观察B线程,所有的操作都是无序的。在JMM中为了提升程序的执行效率,允许编译器和处理器对指令重排序。对于单线程来说,指令重排并不会产生问题,而在多线程下则不可以。
在Java中可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。