线程得原子性、可见性
原子性
定义:原子的字面意思是不可分割的。对于涉及访问共享变量的操作,若该操作从其执行线程以外的任意线程来看是不可分割的,那么该操作就是原子操作,相应的我们称该操作具有原子性。
所谓不可分割,其中一个含义是指访问(读、写)共享变量的操作从其执行线程以外的任何线程来看,该操作要么已经执行结束要么还没开始执行,即其他线程不会看到该操作执行的中间效果。
其二是如果两个原子操作同时访问某个共享变量,那么其中一个线程执行期间,另外得线程无法执行,也就是说访问同一组共享变量得原子操作是不能被交的。
下面通过一个例子来体会一下不可分割的含义:
/**
* @ClassName AtomicTest
* @Description TODO
* @Author liuyi
* @Date 2020/8/5 22:42
* @Version 1.0
*/
public class AtomicTest {
public static void main(String[] args) {
for (int i = 0; i <100 ; i++) {
HostInfo hostInfo = new HostInfo("127.0.0.0",8080);
//更新端口
new Thread(()->{
hostInfo.setIp("127.0.0.1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
hostInfo.setPort(8082);
}).start();
//连接端口
new Thread(()->hostInfo.connect()).start();
}
}
}
class HostInfo{
private String ip;
private int port;
public HostInfo(String ip, int port) {
this.ip = ip;
this.port = port;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
private void update(String ip, int port){
this.ip = ip;
this.port = port;
}
public void connect(){
System.out.println(this.ip+":"+this.port);
}
}
我们来模拟通过ip和端口去连接某个服务,连接之前我们先获取服务的ip和端口,假设我们开启两个线程,一个是更新端口的线程,一个是连接服务端口的线程,
可以看到,我们去连接服务的时候,可能会出现只更新了ip,端口还没更新,很明显这不是我们想要的结果,所以,更新ip和端口不是一个原子操作。
那怎么保证操作的原子性呢,java有两种方式来实现原子性,一是通过加锁(lock)的方式,锁具有排他性,它能保证一个共享变量在任意时刻只能被一个线程访问。
另外一种是利用处理器提供的CAS指令,CAS指令实现原子性的方式与锁实现原子性的方式实质上是相同的,区别在于锁是在软件这一层次实现,而CAS是直接在硬件
(内存和处理器)这一层实现,也可以被看作硬件锁。
可见性:
在多线程环境下,一个线程对某个共享变量进行更新后,后续访问该变量的线程可能无法立刻读取到这个更新的结果,甚至永远也无法读取到这个更新结果。这就是线程
安全问题的另外一个表现形式:可见性。
举个简单的例子,模拟比赛的场景,启动一个线程进行比赛,当裁判判定某个对赢得比赛就将flag设置为-1,然后线程根据判断flag得值是否等于-1去中止比赛。
/** * @Author liuyi * @Description //TODO * @Date 22:30 2020/8/8 * @Param * @return **/ public class Test { public static void main(String[] args) { Match match = new Match(); match.start(); try { Thread.sleep(1000); } catch (Exception e) { } match.flag= -1; System.out.println("change....."); } } class Match extends Thread { public int flag = 0; @Override public void run() { System.out.println("Match Start"); while (true) { if (flag == -1) { break; } } System.out.println("Match End"); } }
但是我们来看代码得运行结果:
根本没有打印比赛结束,并且程序一直在运行中,这说明flag对于Match线程来讲,根本没有更新为-1。所以此时,共享变量flag对于Match线程不具备可见性。
那么如何实现可见性呢,对共享变量加volalite关键字即可实现可见性。