JMM 的关键技术点都是围绕着多线程的原子性、可见性和有序性来创建的。所以,咱们首先必须了解这些概念。java
1.原子性(Atomicity)多线程
原子性是指一个操做是不可中断的。即便是在多个线程一块儿执行的时候,一个操做旦开始,就不会被其余线程干扰。并发
for example:ide
对于一个静态int类型变量,当有多个线程同时操做修改其值时,它的值无非那几个之一,可是对于long类型的,在32位操做系统就可能会出现,结果形成干扰。性能
2.可见性(Visibility)优化
可见性是指当一个线程修改了某一个共享变量的值时,其余线程是否可以当即知道这个修改。spa
显然,对于串行程序来讲,可见性问题是不存在的。由于你在任何一个操做步骤中修改了某个操作系统
变量,在后续的步骤中读取这个变量的值时,读取的必定是修改后的新值。可是在多线程并发线程
的状况下,若是修改了,那么其余线程不必定能马上发现这个改动code
for example:
Thread Thread 2
r1=p; r6=p;
r2=r1.X; r6. X=3
r3=9
r4=r3. X;
r5=r1. X;
这里假设在初始时,p=q 而且 p. X=0。对于大部分编译器来讲,可能会对线程 1 进行向前替换的优化,也就是 r5=r1. X 这条指令会被直接替换成 rS=r2。因为它们都读取了 r1. X,又发生在同一个线程中,所以,编译器极可能认为第 2 次读取是彻底没有必要的。所以,上述指令可能会变成
Thread Thread 2
r1=p; r6=p;
r2=r1.X; r6. X=3
r3=9
r4=r3. X;
r5=r2;
如今思考这么一种场景。假设线程 2 中的 r6. X=3 发生在 r2=rl. X 和 r4=r3. X 之间,而编译器又打算重用 r2 来表示 rS,那么就有可能出现很是奇怪的现象。你看到的 r2 是 0, r4 是 3, 可是 r5 仍是 0。所以,若是从线程 1 代码的直观感受上看就是:p. X 的值从 0 变成了 3(由于 r4 是 3),接着又变成了 0(这是否是算一个很是怪异的问题呢?)。
3.有序性(Ordering)
有序性问题的缘由是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。下面来看一个简单的例子
Class Orderexample { int a=0 Boolean flag falser public void writer () { a=1 flag=true; } public void reader () { if (flag) { int i= a +1; ... } } }
假设线程 A 首先执行 writer 方法,接着线程 B 执行 reader 方法,若是发生指令重排,那么线程 B 在代码第 10 行时,不必定能看到 a 已经被赋值为 1 了。
分门别类的管理-线程组
在一个系统中,若是线程数量不少,并且功能分配比较明确,就能够将相同功能的线程放置在同一个线程组里。打个比方,若是你有一个苹果,你能够把它拿在手里,可是若是你有十个苹果,你最好还有一个篮子,不然不方便携带。对于多线程来讲,也是这个道理。想要轻松处理几十个甚至上百个线程,最好仍是将它们都装进对应的篮子里。
Threadgroup tg =new Threadgroup ("Printgroup");//定义一个线程组 Thread tl = new Thread (tg, new Threadgroupname (), "T1"); //将T1线程放入线程组 Thread t2 =new Thread (tg, new Threadgroupname (), " T2") t1.start(); t2.start(); System.out.println (tg.activeCount ());//得到活动线程的总数 tg.list () ;//打印全部的线程信息
守护线程(加在线程启动前)
/** * 守护线程 */ public class DaemonThread { public static class Daemont extends Thread{ @Override public void run() { while (true){ System.out.println("live"); try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Daemont(); //Daemont线程就成了守护线程了,在主线程休眠后退出,守护线程也随之退出,而且须要加在线程启动前 //若是不加上的这个的话,在主线程退出后,Daemont线程还会一直执行下去, thread.setDaemon(true); thread.start(); Thread.sleep(5000); } }
Volatile与synchronized
Volatile
Volatile能够看作是一个轻量级的synchronized,它能够在多线程并发的状况下保证变量的“可见性”,什么是可见性?
就是在一个线程的工做内存中修改了该变量的值,该变量的值当即能回显到主内存中,从而保证全部的线程看到这
个变量的值是一致的。因此在处理同步问题上它大显做用,并且它的开销比synchronized小、使用成本更低。
synchronized
synchronized叫作同步锁,是Lock的一个简化版本,因为是简化版本,那么性能确定是不如Lock的,不过它操做起来方便,
只须要在一个方法或把须要同步的代码块包装在它内部,那么这段代码就是同步的了,全部线程对这块区域的代码访问
必须先持有锁才能进入,不然则拦截在外面等待正在持有锁的线程处理完毕再获取锁进入,正由于它基于这种阻塞的策
略,因此它的性能不太好,可是因为操做上的优点,只须要简单的声明一下便可,并且被它声明的代码块也是具备操做的
原子性。
总结:关于Volatile关键字具备可见性,但不具备操做的原子性,而synchronized比volatile对资源 的消耗稍微大点,但能够保证变量操做的原子性,保证变量的一致性,最佳实践则是两者结合一块儿使用。