static boolean flag;
public static void main(String… args) {
new Thread1().start();
new Thread2().start();
}
static class Thread1 extends Thread {
@Override
public void run() {
while (true) {
if (flag) {
flag = false;
System.out.println(“Thread1 set flag to false”);
}
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
while (true) {
if (!flag) {
flag = true;
System.out.println(“Thread2 set flag to true”);
}
}
}
}
}
这段代码真的非常简单,我们开启了两个线程来对同一个变量flag进行修改。Thread1使用一个while(true)循环,发现flag是true时就把它改为false。Thread2也使用一个while(true)循环,发现flag是false时就把它改为true。
理论上来说,这两个线程同时运行,那么就应该一直交替打印,你改我的值,我再给你改回去。
实际上真的会是这样吗?我们来运行一下就知道了。
可以看到,打印过程只持续了一小会就停止打印了,但是程序却没有结束,依然显示在运行中。
这怎么可能呢?理论上来说,flag要么为true,要么为false。true的时候Thread1应该打印,false的时候Thread2应该打印,两边都不打印是为什么呢?
我们用刚才所学的知识就可以解释这个原本解释不了的问题,因为Thread1和Thread2的CPU高速缓存中各有一份flag值,其中Thread1中缓存的flag值是false,Thread2中缓存的flag值是true,所以两边就都不会打印了。
这样我们就通过一个实际的例子演示了刚才所说的可见性问题。那么该如何解决呢?
答案很明显,volatile。
volatile这个关键字的其中一个重要作用就是解决可见性问题,即保证当一个线程修改了某个变量之后,该变量对于另外一个线程是立即可见的。
至于volatile的工作原理,太底层方面的内容我也说不上来,大概原理就是当一个变量被声明成volatile之后,任何一个线程对它进行修改,都会让所有其他CPU高速缓存中的值过期,这样其他线程就必须去内存中重新获取最新的值,也就解决了可见性的问题。
我们可以将刚才的代码进行如下修改:
public class Main {
volatile static boolean flag;
…
}
没错,就是这么简单,在flag变量的前面加上volatile关键字即可。然后重新运行程序,效果如下图所示。
一切如我们所预期的那样运行了。
指令重排问题
volatile关键字还有另外一个重要的作用,就是禁止指令重排,这又是一个非常有趣的问题。
我们先来看两段代码:
// 第一段代码
int a = 10;
int b = 5;
a = 20;
System.out.println(a + b);
// 第二段代码
int a = 10;
a = 20;
int b = 5;
System.out.println(a + b);
第一段代码,我们声明了一个a变量等于10,又声明了一个b变量等于5,然后将a变量的值改成了20,最后打印a + b的值。
第二段代码,我们声明了一个a变量等于10,然后将a变量的值改成了20,又声明了一个b变量等于5,最后打印a + b的值。
这两段代码有区别吗?
不用瞎猜了,这两段代码没有任何区别,声明变量b和修改变量a之间的顺序是随意的,它们之间谁也不碍着谁。
也正是因为这个原因,CPU在执行代码时,其实并不一定会严格按照我们编写的顺序去执行,而是可能会考虑一些效率方面的原因,对那些先后顺序无关紧要的代码进行重新排序,这个操作就被称为指令重排。
这么看来,指令重排这个操作没毛病啊。确实,但只限在单线程环境下。
很多问题一旦进入了多线程环境,就会变得更加复杂,我们来看如下代码:
public class Main {
static boolean init;
static String value;
static class Thread1 extends Thread {
@Override
public void run() {
value = “hello world”;
init = true;
}
}
static class Thread2 extends Thread {
@Override
public void run() {
while (!init) {
// 等待初始化完成
}
value.toUpperCase();
}
}
}
这段代码的思路仍然很简单,Thread1用于对value数据进行初始化,初始化完成之后会将init设置成true。Thread2则会先通过while循环等待初始化完成,完成之后再对value数据进行操作。
那么这段代码可以正常工作吗?未必,因为根据刚才的指令重排理论,Thread1中value和init这两个变量之间是没有先后顺序的。如果CPU将这两条指令进行了重排,那么就可能出现初始化已完成,但是value还没有赋值的情况。这样Thread2的while循环就会跳出,然后在操作value的时候出现空指针异常。
所以说,指令重排功能一旦进入了多线程环境,也是可能会出现问题的。
而至于解决方案嘛,当然还是volatile了。
对某个变量声明了volatile关键字之后,同时也就意味着禁止对该变量进行指令重排。所以我们只需要这样修改代码就能够保证程序的安全性了。
public class Main {
volatile static boolean init;
…
}
volatile在Android上的应用
现在我们已经了解了volatile关键字的主要作用,但是就像开篇时那位朋友提到的一样,很多人想不出来这个关键字在Android上有什么用途。
其实我觉得任何一个技术点都不应该去生搬硬套,你只要掌握了它,该用到时能想到它就可以了,而不是绞尽脑汁去想我到底要在哪里使用它。
我在看一些Google库的源码时,其实时不时就能看到这个关键字,只要是涉及多线程编程的时候,volatile的出场率还是不低的。
这里我给大家举一个常见的示例吧,在Android上我们应该都编写过文件下载这个功能。在执行下载任务时,我们需要开启一个线程,然后从网络上读取流数据,并写入到本地,重复执行这个过程,直到所有数据都读取完毕。
那么这个过程我可以用如下简易代码进行表示:
public class DownloadTask {
public void download() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
byte[] bytes = readBytesFromNetwork(); // 从网络上读取数据
if (bytes.length == 0) {
break; // 下载完毕,跳出循环
}
writeBytesToDisk(bytes); // 将数据写入到本地
}
}
}).start();
}
}
到此为止没什么问题。
不过现在又来了一个新的需求,要求允许用户取消下载。我们都知道,Java的线程是不可以中断的,所以如果想要做取消下载的功能,一般都是通过标记位来实现的,代码如下所示:
public class DownloadTask {
boolean isCanceled = false;
public void download() {
new Thread(new Runnable() {
@Override
public void run() {
while (!isCanceled) {
byte[] bytes = readBytesFromNetwork();
if (bytes.length == 0) {
break;
}
writeBytesToDisk(bytes);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
写在最后
对程序员来说,很多技术的学习都是“防御性”的。也就是说,我们是在为未来学习。我们学习新技术的目的,或是为了在新项目中应用,或仅仅是为了将来的面试。但不管怎样,一定不能“止步不前”,不能荒废掉。
![
文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
存中…(img-8ppRCsvS-1713543839512)]
[外链图片转存中…(img-MuBk6EbU-1713543839513)]
文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!