多线程编程要保证满足三个特性:原子性、可见性、有序性
为了保证多线程的三个特性,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,去到源码看看
可以看到在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时,基本上都会为线程调用一个initialValue
i++是一个非线程安全的操作,演示一个多线程对同一个变量++的例子
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
}
}
原文:https://www.cnblogs.com/JIATCODE/p/13276573.html