多线程编程要保证知足三个特性:原子性、可见性、有序性java
为了保证多线程的三个特性,java引入了不少线程控制机制数据库
模拟一个银行转帐编程
public class ThreadLocalDemo { //建立一个银行,钱,取款,存款 static class Bank{ //使用ThreadLocal建立该变量,数据类型以泛型传入 private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ //使用initialValue初始化该值,每一个线程获得该变量副本时,都是这个初始值 @Override protected Integer initialValue() { return 0; } }; //为该变量副本赋值 public void set(Integer money){ threadLocal.set(threadLocal.get()+money); } //获取该变量副本 public Integer get(){ return threadLocal.get(); } } //建立线程,执行转帐 static class Transfer implements Runnable{ //银行对象,封装转帐操做 private Bank bank; public Transfer(Bank bank) { this.bank = bank; } //模拟转帐 @Override public void run() { for(int i =0 ;i<10;i++){ bank.set(10); System.out.println(Thread.currentThread().getName()+"帐户余额:"+bank.get()); } } } public static void main(String[] args) { Bank bank = new Bank(); Thread thread1 = new Thread(new Transfer(bank),"用户A"); Thread thread2 = new Thread(new Transfer(bank),"用户B"); thread1.start(); thread2.start(); } }
先从TreadLocal自己讲起,先看到get方法(set方法相似)数组
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //拿到当前线程中的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { //取出值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //返回结果 return result; } } //若是当前线程中的ThreadLocalMap为空,那么对线程的ThreadLocalMap作一些初始化处理 return setInitialValue(); } //初始化处理 private T setInitialValue() { //返回ThreadLocal对象建立时的默认值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } //该方法通常在建立时被重写 protected T initialValue() { return null; }
由get方法可知,在线程中,是已经有了一个ThreadLocalMap对象的,那ThreadLocalMap对象是什么呢安全
如图,可知ThreadLocalMap内部其实是有一个Entry对象来存放数据多线程
ThreadLocal的get和set都是获取当前线程,而后对其内部的ThreadLocalMap进行数据操做,也就是说在线程中的是已经初始化了一个ThreadLocalMap,去到源码看看ide
能够看到在Thread中有不少的构造器,拿咱们上述例子的构造器来讲优化
//线程类中就组合了一个ThreadLocalMap对象 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //构造器 Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } //注意到这里有一个inheritThreadLocals的布尔值 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ………… //获取当前线程(在例子中为主线程) Thread parent = currentThread(); ………… //若是当前线程中存在ThreadLocalMap对象,则将其元素也赋值到新建线程的ThreadLocalMap对象中 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ………… }
//ThreadLocal类 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } //能够看到,就是将当前线程的ThreadLocalMap给到新建的线程 private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
小结:也就是说,重写的initialValue方法,是当调用get和set时,为线程中的ThreadLocalMap进行一个初始化的定制,在第一个调用get和set时,基本上都会为线程调用一个initialValuethis
i++是一个非线程安全的操做,演示一个多线程对同一个变量++的例子atom
public class ThreadAtomicDemo { static private int n; public static void main(String[] args) throws InterruptedException { int j=0; while (j<100){ n=0; Thread thread1 = new Thread(new Runnable() { @Override public void run() { for(int i =0;i<3000;i++){ n++; } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for(int i =0;i<3000;i++){ n++; } } }); thread1.start(); thread2.start(); //加入主线程,主线程会等待该线程完成后继续进行 thread1.join(); thread2.join(); System.out.println("n最终结果:"+n); j++; } } }
结果以下:
能够看到,结果不是稳定的6000,而是会出现少加多加的状况,由于i++实际在cpu中是分了三步,这是非原子类操做,多线程的状况下就容易出现问题
tp1 = i; tp2 = i+1; i = tp2;
Integer有对应的原子类AtomicInteger:赋值改成 n=new AtomicInteger(0) ;n++改成n.getAndIncrement() 用get()取出值
java的java.util.concurrent.atomic包里提供了不少能够进行原子操做的类
用代码演示:
public class Reentrant { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); for (int i =0 ;i<10;i++){ lock.lock(); System.out.println("加锁次数:"+(i+1)); } for (int i =0 ;i<10;i++){ try { System.out.println("解锁次数:"+(i+1)); }finally { lock.lock(); } } } }
结果发现,重入锁能够被已拥有该锁的线程重复获取
多个线程能够同时读,可是读的时候不能写;多个线程不能够同时写,写的时候也不能读
package com.JIAT.deadLock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWrite { private Map<String,String> map = new HashMap<String, String>(); private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); public String get(String key){ readLock.lock(); try{ System.out.println("读操做已经加锁,开始读操做...."); Thread.sleep(3000); return map.get(key); }catch (InterruptedException e){ e.printStackTrace(); return null; }finally { System.out.println("读操做已经解锁"); readLock.unlock(); } } public void set(String key , String value){ writeLock.lock(); try { System.out.println("写操做已经加锁,开始写操做"); Thread.sleep(3000); map.put(key,value); }catch (InterruptedException e){ e.printStackTrace(); }finally { System.out.println("写操做完成,已经解锁"); writeLock.unlock(); } } public static void main(String[] args) { final ReadWrite readWrite = new ReadWrite(); //主线程写操做 readWrite.set("key","value"); //线程一读操做 new Thread(){ @Override public void run() { System.out.println(readWrite.get("key")); } }.start(); //线程二写操做 new Thread(){ @Override public void run() { readWrite.set("key1","value1"); } }.start(); //线程三读操做 new Thread(){ @Override public void run() { System.out.println(readWrite.get("key")); } }.start(); } }
运行结果咱们能够看到,当线程一读的时候,线程二是不能进行写的,线程一读完毕,线程二开始写,而此时线程三也不能读,只有等线程二写完了才能够读
一个共享变量被Volatile修饰以后,那么就具有了两层语义
保证了不一样线程对这个变量操做的可见性,当一个线程对其进行操做,对于其余线程来讲是当即可见的(不保证原子性)
禁止进行指令重排序(保证变量所在行的有序性)
什么是指令重排序?
例如
int i = 0 ; i=1;i=2;i=3; //java会直接运行i=3忽略了前两句,由于编译器认为这是无效操做,会自动进行优化重排序咱们的代码指令 Volatile int i = 0 ; i=1;i=2;i=3; //java会按照咱们的指令逐个进行,再也不重排序指令
基于Volatile做用,使用Volatile时须要知足如下两点:
例如单例模式的双重校验
class Singleton{ private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //看看是否已经建立 if(instance==null){ //只让单线程进行建立 synchronized(Singleton.class){ if(instance==null){ //此时若是没有volatile关键字马上刷新,那其余线程有可能来不及看到最新的instance就已经经过第一层校验,就会再建立一个instance instance = new Singleton(); } } } return instance } }