该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽可能按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其余的优质博客,在此向各位大神表示感谢,膜拜!!!java
从本篇博文开始Android并发编程系列。因为笔者水平有限,若是博文之中有任何错误或者纰漏之处,还请不吝赐教。程序员
在Android SDK中并无提供新颖的线程实现方案,使用的依旧是JDK中的线程。在Java中开启新线程有3中常见的方式编程
public class ThreadA extends Thread { @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()); } } //测试的主线程 public class Main { public static void main(String[] args){ ThreadA threadA = new ThreadA(); threadA.setName("threadA"); threadA.start(); System.out.println("主线程"+Thread.currentThread().getName()); } }
public class ThreadB implements Runnable{ @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } //测试的主线程 public class Main { public static void main(String[] args){ ThreadB threadB = new ThreadB(); //注意这里启动的方式跟方式1不同 Thread thread = new Thread(threadB); thread.setName("threadB"); thread.start(); System.out.println("主线程"+Thread.currentThread().getName()); } }
public class ThreadC implements Callable<String> { @Override public String call() throws Exception { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } return Thread.currentThread().getName(); } } public class Main { public static void main(String[] args){ ThreadC threadC = new ThreadC(); //FutureTask 后续会讲到,先知道有怎么个实现方式 FutureTask<String> feature = new FutureTask<>(threadC); //注意启动方式有点不同; Thread thread1 = new Thread(feature); thread1.setName("threadC"); thread1.start(); //注意细细体会这个,只有主线程get了,主线程才会继续往下面执行 try { System.out.println(feature.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("主线程"+Thread.currentThread().getName()); } }
上面简单的介绍了3种开启线程的方式,接下来咱们来看一下Java的内存模型,由于后续文章讲到的许多知识都须要这个做为基础。缓存
JMM规定JVM有主内存(Main Memory)和工做内存(Working Memory),主内存其实就是咱们日常所说的Java堆内存,存放全部类实例变量等,这部份内存是多个线程共享的;工做内存里存放的则是线程从主内存拷贝过来的变量以及访问方法获得的临时变量,这部份内存为线程私有,其余的线程不能访问。多线程
注:上面所说的拷贝并非拷贝整个对象实例到工做内存,虚拟机可能拷贝对象引用或者对象字段,而不是整个对象。
主内存与工做内存的关系以下图所示并发
主内存与工做内存之间具体的交互协议,被定义了如下8种操做来完成,虚拟机实现时必须保证每一种操做都是原子的、不可再分的。app
8种操做的实现规则:ide
对一个变量执行unlock操做以前,必须将此变量同步回主内存中(执行store、write)。函数
整个并发编程所遇到的问题能够说是如下三个问题的变种。测试
可见性问题
由上图主内存与工做内存的关系图可知,线程不与主内存进行直接交互,而是把主内存的实例变量拷贝一份到线程的工做内存中进行操做,而后再同步给主内存。之因此这样作,是由于工做内存大都由高速缓存、寄存器这类比主内存存取速度更快的内存担当,以便弥补CPU速度与主内存存取速度不在一个数量级的差距。
注:当线程操做某个对象时,执行顺序以下:
1 从主存复制变量到当前工做内存(read -> load)
2 执行代码改变共享变量的值(use -> assign)
3 用工做内存的数据刷新主存相关内容(store -> write)
因此单个线程与线程的工做内存之间就有了相互的隔离效果,专业术语称之为“可见性问题”
可见性是指当一个线程修改了共享变量的值,其余线程可以当即得知这个修改,可见性由volatile支持,除了volatile之外,synchronize和final关键字,synchronize的可见性是由”对一个变量执行unlock操做以前,必须先把此变量同步回主内存中“这条规则保证的,而final关键字是指当final修饰的字段在构造函数中一旦初始化完成,而且构造器没有把this的引用传递出去,那在其余线程中就能看见final字段的值,无须同步就能被其余线程正确访问
在JMM中,若是一个操做执行的结果须要对另外一个操做可见,那么这两个操做之间必需要存在happens-before关
系。这里提到的两个操做既能够是在一个线程以内,也能够是在不一样线程之间。
与程序员密切相关的happens-before规则以下。
注意
两个操做之间具备happens-before关系,并不意味着前一个操做必需要在后一个操做以前执行!happens-before仅仅要求前一个操做(执行的结果)对后一个操做可见,且前一个操做按顺序排在第二个操做以前(the first is visible to and ordered before the second)。
JMM的happens-before规则不但简单易懂,并且也向程序员提供了足够强的内存可见性保证
本篇文章简单分析了下线程的启动方式以及JMM模型,为后面的文章铺垫一下。
Java多线程与锁
此致,敬礼