Java 并发

来自《Java 并发编程之美》

只是摘抄,方便回顾,具体可以看书

进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位

线程是进程的一个执行路径,CPU分配的基本单位

一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域

程序计数器:记录该线程让出CPU时的执行地址的

线程创建与运行

Thread

class ThreadTest {
   //继承Thread类并重写run方法
   public static class MyThread extends Thread {
       // 在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法
      @Override
      public void run() {
         System.out.println("I am a child thread");
      }
   }
   public static void main(String[] args) {
      // 创建线程	创建完thread对象后该线程并没有被启动执行
      MyThread thread = new MyThread();
      // 启动线程	直到调用了start方法后才真正启动了线程
      thread.start();
   }
}

Runnable

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("I am a child thread");
    }
}).start();

FutureTask

有返回值

//创建任务类,类似Runable
public static class CallerTask implements Callable<String> {
	@Override
	public String call() throws Exception {
		return "hello";
	}
}
public static void main(String[] args) throws InterruptedException {
	// 创建异步任务
	FutureTask<String> futureTask  = new FutureTask<>(new CallerTask());
	//启动线程
	new Thread(futureTask).start();
	try {
		//等待任务执行完毕,并返回结果
		String result = futureTask.get();
		System.out.println(result);
	} catch (ExecutionException e) {
		e.printStackTrace();
	}
}

线程获取共享变量的监视器锁

执行synchronized同步代码块时,使用该共享变量作为参数

synchronized(共享变量){
      //doSomething
  }

调用该共享变量的方法,并且该方法使用了synchronized修饰

synchronized void add(int a, int b){
      //doSomething
}

虚假唤醒

一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒

不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待

synchronized (obj) {
    while (条件不满足){
        obj.wait();
    }
}

notify() 函数

唤醒共享变量上调用wait系列方法后被挂起的线程(必须在获取了共享对象的监视器锁后才可以返回,存在竞争只能获取一个)

notifyAll() 函数:唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程

join

等待某几件事情完成后才能继续往下执行

yield方法

请求让出自己的CPU使用

interrupted

interrupted()方法 检测当前线程是否被中断,如果发现当前线程被中断,则会清除中断标志

public static void main(String[] args) throws InterruptedException {
   Thread threadOne = new Thread(new Runnable() {
      public void run() {
         for (; ; ) {
         }
      }
   });
   //启动线程
   threadOne.start();
   //设置中断标志
   threadOne.interrupt();
   //获取中断标志	true
   System.out.println("isInterrupted:" + threadOne.isInterrupted());
   //获取中断标志并重置	获取的是主线程的中断标志	false
   System.out.println("isInterrupted:" + threadOne.interrupted());
   //获取中断标志并重置	主线程没有中断	false
   System.out.println("isInterrupted:" + Thread.interrupted());
   //获取中断标志	 true
   System.out.println("isInterrupted:" + threadOne.isInterrupted());
   threadOne.join();
}

守护线程与用户线程

用户线程未结束,正常情况 JVM 就不会退出

设置守护线程

thread.setDaemon(true);
thread.start();

ThreadLocal

ThreadLocal原理及内存泄露预防

ThreadLocal变量:访问这个变量的每个线程都会有这个变量的一个本地副本

Thread类中

ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;

每个线程的ThreadLocal变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面

Thread里面的threadLocals为何被设计为map结构?

很明显是因为每个线程可以关联多个ThreadLocal变量

每个Thread都有一个threadLocals变量,它就是放k-vmap,类型为ThreadLocalMap

这个mapentryThreadLocal.ThreadLocalMap.Entry

具体的keyvalue类型分别是

  • ThreadLocal(我们定义ThreadLocal变量就是在定义这个key)
  • Object(我们定义ThreadLocal变量的值就是在定义这个value)

实际上key是指向ThreadLocal类型变量的弱引用WeakReference<ThreadLocal<?>>,但可以先简单理解为ThreadLocal

set

public void set(T var1) {
    Thread var2 = Thread.currentThread();
    // threadLocals 是否存在
    ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
    if (var3 != null) {
        var3.set(this, var1);
    } else {
        this.createMap(var2, var1);
    }

}
// 线程自己的变量 threadLocals
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
    return var1.threadLocals;
}
void createMap(Thread var1, T var2) {
    var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
}

get

public T get() {
    Thread var1 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
	// threadLocals 存在,返回对应本地变量的值
    if (var2 != null) {
        ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
        if (var3 != null) {
            Object var4 = var3.value;
            return var4;
        }
    }

    return this.setInitialValue();
}

返回本地变量的值

private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {
    int var2 = var1.threadLocalHashCode & this.table.length - 1;
    ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];
    return var3 != null && var3.get() == var1 ? var3 : this.getEntryAfterMiss(var1, var2, var3);
}

初始化 threadLocals new ThreadLocal.ThreadLocalMap(Thread.currentThread(), null);

private T setInitialValue() {
    Object var1 = this.initialValue();	// return null;
    Thread var2 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
    if (var3 != null) {
        var3.set(this, var1);
    } else {
        // new ThreadLocal.ThreadLocalMap(Thread.currentThread(), null);
        this.createMap(var2, var1);
    }
    return var1;
}

remove

public void remove() {
    ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
    if (var1 != null) {
        var1.remove(this);
    }
}
private void remove(ThreadLocal<?> var1) {
    ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
    int var3 = var2.length;
    int var4 = var1.threadLocalHashCode & var3 - 1;

    for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
        if (var5.get() == var1) {
            var5.clear();
            this.expungeStaleEntry(var4);
            return;
        }
    }

}

ThreadLocal 不支持继承

//(1)创建线程变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
   //(2) 设置线程变量
   threadLocal.set("hello world");
   //(3) 启动子线程
   Thread thread = new Thread(new Runnable() {
      public void run() {
         //(4) 子线程输出线程变量的值	获取不到在父线程设置的值
         System.out.println("thread:" + threadLocal.get());
      }
   });
   thread.start();
   //(5) 主线程输出线程变量的值
   System.out.println("main:" + threadLocal.get());
}

main:hello world
thread:null

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的

在子线程thread里面调用get方法时当前线程为thread线程

而这里调用set方法设置线程变量的是main线程,两者是不同的线程,自然子线程访问时返回null

InheritableThreadLocal:让子线程能访问到父线程中的值

共享变量内存

Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量
在这里插入图片描述

共享变量内存不可见:工作内存值不统一
共享变量读取到工作内存未来得及写到主存,变量又被另一个线程读取

synchronized:擦除工作内存值,从主存获取
释放刷新主存

volatile:跳过工作内存,
不保证操作原子性,会重复读取同一值
使用条件①不依赖变量当前值②不加锁(没用)

Unsafe

提供了硬件级别的原子性操作

static final Unsafe unsafe;
static final long stateOffset;
private volatile long state = 0;
static {
   try {
      //使用反射获取 Unsafe 的成员变量 theUnsafe
      Field field = Unsafe.class.getDeclaredField("theUnsafe");
      System.out.println("field = " + field.toString());
      // 设置为可存取
      field.setAccessible(true);
      // 获取该变量的值
      Object o = field.get(null);
      unsafe = (Unsafe) field.get(null);
      System.out.println("unsafe = " + unsafe.toString());
      //获取state在TestUnSafe中的偏移量
      stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.
            getDeclaredField("state"));
   } catch (Exception ex) {
      System.out.println(ex.getLocalizedMessage());
      throw new Error(ex);
   }
}
public static void main(String[] args) {
   TestUnSafe test = new TestUnSafe();
   Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
   System.out.println(sucess);
}

Java指令重排序

public static class ReadThread extends Thread {
   public void run() {
      while(! Thread.currentThread().isInterrupted()){
         if(ready){//(1)
            System.out.println(num+num); //(2)      输出结果不一定为4
         }
         System.out.println("read thread....");
      }
   }
}
public static class Writethread extends Thread {
   public void run() {
      num = 2; //(3)
      ready = true; //(4)		如果先执行,那么(2)可能输入 0 
      System.out.println("writeThread set over...");
   }
}
private static int num =0;
private static boolean ready = false;
public static void main(String[] args) throws InterruptedException {
	ReadThread rt = new ReadThread();
	rt.start();
	Writethread  wt = new Writethread();
	wt.start();
	Thread.sleep(10);
	rt.interrupt();
	System.out.println("main exit");
}

伪共享

伪共享:多个变量被放入了一个缓存行中,并且多个线程同时去写入缓存行中不同的变量,由于同时只能有一个线程操作缓存行,所以相比将每个变量放到一个缓存行,性能会有所下降

为何多个变量会被放入一个缓存行呢?

缓存与内存交换数据的单位就是缓存行

CPU要访问的变量没有在缓存中找到时,根据程序运行的局部性原理,会把该变量所在内存中大小为缓存行的内存放入缓存行

如何避免伪共享

字节填充:创建一个变量时使用填充字段填充该变量所在的缓存行

// FilledLong是一个类对象,而类对象的字节码的对象头占用8字节
public final static class FilledLong {
    // value变量占用8字节
    public volatile long value = 0L;	
    // 6个long类型的变量,每个long类型变量占用8字节
    public long p1, p2, p3, p4, p5, p6;		
} 
// 总共 (1+1+6)x 8 = 64 字节

JDK 8提供了一个sun.misc.Contended注解,用来解决伪共享问题。将上面代码修改为如下。

@sun.misc.Contended
public final static class FilledLong {
    public volatile long value = 0L;
}

锁的概述

乐观锁与悲观锁

悲观锁:数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态

乐观锁:认为数据一般不会造成冲突,访问记录前不会加排他锁

Random

Random random = new Random();
// 0~5(包含0,不包含5)之间的随机数
random.nextInt(5);

public int nextInt(int var1) {
    if (var1 <= 0) {
        throw new IllegalArgumentException("bound must be positive");
    } else {
        // 根据老的种子生成新的种子
        int var2 = this.next(31);
        // ...	根据新的种子计算随机数
    }
}

protected int next(int var1) {
    AtomicLong var6 = this.seed;

    long var2;
    long var4;
    do {
        // 获取当前原子变量的种子
        var2 = var6.get();
        var4 = var2 * 25214903917L + 11L & 281474976710655L;
        // 保证只有一个线程可以更新老的种子为新的
    } while(!var6.compareAndSet(var2, var4));

    return (int)(var4 >>> 48 - var1);
}

CAS 导致大量线程进行自旋测试,降低并发性能,所以ThreadLocalRandom应运而生。

ThreadLocalRandom

每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新种子更新老的种子,再根据新种子计算随机数,就不会存在竞争问题

ThreadLocalRandom random =  ThreadLocalRandom.current();
random.nextInt(5);

AtomicLong

// get 原子性设置 value 值为原始值 +1 之后返回原始值
public final long getAndIncrement() {
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}

public final long getAndDecrement() {
    return unsafe.getAndAddLong(this, valueOffset, -1L);
}

public final long getAndAdd(long var1) {
    return unsafe.getAndAddLong(this, valueOffset, var1);
}

// xxxGet 原子性设置 value 值为原始值 +1 后返回递增值
public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

public final long decrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}

public final long addAndGet(long var1) {
    return unsafe.getAndAddLong(this, valueOffset, var1) + var1;
}

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

具体例子

//(10)创建Long型原子计数器
private static final AtomicLong atomicLong = new AtomicLong();
//(11)创建数据源
private static final Integer[] arrayOne = new Integer[]{0,1,2,3,0,5,6,0,56,0};
private static final Integer[] arrayTwo = new Integer[]{10,1,2,3,0,5,6,0,56,0};
public static void main( String[] args ) throws InterruptedException
{
   //(12)线程one统计数组arrayOne中0的个数
   Thread threadOne = new Thread(new Runnable() {
      @Override
      public void run() {
         int size = arrayOne.length;
         for (Integer integer : arrayOne) {
            if (integer == 0) {
               atomicLong.incrementAndGet();
            }
         }
      }
   });
   //(13)线程two统计数组arrayTwo中0的个数
   Thread threadTwo = new Thread(new Runnable() {
      @Override
      public void run() {
         int size = arrayTwo.length;
         for (Integer integer : arrayTwo) {
            if (integer == 0) {
               atomicLong.incrementAndGet();
            }
         }
      }
   });
   //(14)启动子线程
   threadOne.start();
   threadTwo.start();
   //(15)等待线程执行完毕
   threadOne.join();
   threadTwo.join();
   System.out.println("count 0:" + atomicLong.get());
}

LongAdder

AtomicLong的性能瓶颈:过多线程同时去竞争一个变量的更新

LongAdder:把一个变量分解为多个变量,让同样多的线程去竞争多个资源

多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。

LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。

由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。

(1)LongAdder的结构是怎样的?

(2)当前线程应该访问Cell数组里面的哪一个Cell元素?

(3)如何初始化Cell数组?

(4)Cell数组如何扩容?

(5)线程访问分配的Cell元素有冲突后如何处理?

(6)如何保证线程操作被分配的Cell元素的原子性?

transient关键字: 将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化

// Striped64 类中
transient volatile Striped64.Cell[] cells;
transient volatile long base;
transient volatile int cellsBusy;

LongAdder 类中

public void add(long var1) {
    Cell[] var3;
    long var4;
    // 一:cells 不为空 或 二:cells为空,执行 CAS 操作
    if ((var3 = this.cells) != null || !this.casBase(var4 = this.base, var4 + var1)) {
        boolean var10 = true;
        long var6;
        int var8;
        Cell var9;
        // cells 为空,之前执行过了 CAS  执行 longAccumulate
        // cells 不为空,cells 长度为大于等于0 ,访问Cell元素访问不存在|  存在,CAS更新Cell的value失败 执行 longAccumulate
        if (var3 == null || (var8 = var3.length - 1) < 0 || (var9 = var3[getProbe() & var8]) == null || !(var10 = var9.cas(var6 = var9.value, var6 + var1))) {
            this.longAccumulate(var1, (LongBinaryOperator)null, var10);
        }
    }

}

// Cell
final boolean cas(long var1, long var3) {
    return UNSAFE.compareAndSwapLong(this, valueOffset, var1, var3);
}

Striped64 类中的 longAccumulate 方法

在这里插入图片描述

final void longAccumulate(long var1, LongBinaryOperator var3, boolean var4) {
    int var5;
    if ((var5 = getProbe()) == 0) {
        // UNSAFE.putLong(var4, SEED, var2); UNSAFE.putInt(var4, PROBE, var1);
        ThreadLocalRandom.current();
        // return UNSAFE.getInt(Thread.currentThread(), PROBE);
        var5 = getProbe();
        var4 = true;
    }

    boolean var6 = false;

    while(true) {
        Striped64.Cell[] var7;
        int var9;
        long var10;
        if ((var7 = this.cells) != null && (var9 = var7.length) > 0) {
            Striped64.Cell var8;
            // 计算要访问的 Cell 元素下标
            if ((var8 = var7[var9 - 1 & var5]) == null) {
                if (this.cellsBusy == 0) {
                    Striped64.Cell var32 = new Striped64.Cell(var1);
                    if (this.cellsBusy == 0 && this.casCellsBusy()) {
                        boolean var33 = false;

                        try {
                            Striped64.Cell[] var14;
                            int var15;
                            int var16;
                            if ((var14 = this.cells) != null && (var15 = var14.length) > 0 && var14[var16 = var15 - 1 & var5] == null) {
                                var14[var16] = var32;
                                var33 = true;
                            }
                        } finally {
                            this.cellsBusy = 0;
                        }

                        if (var33) {
                            break;
                        }
                        continue;
                    }
                }

                var6 = false;
            } else if (!var4) {
                var4 = true;
            } else {
                if (var8.cas(var10 = var8.value, var3 == null ? var10 + var1 : var3.applyAsLong(var10, var1))) {
                    break;
                }
				// 如果 Cell 数组元素个数小于 CPU 个数   (每个CPU都运行一个线程才会使多线程的效果最佳,即 Cell 数组元素个数与CPU个数一致)
                if (var9 < NCPU && this.cells == var7) {
                    if (!var6) {
                        var6 = true;
                    } else if (this.cellsBusy == 0 && this.casCellsBusy()) {  // 扩容
                        try {
                            if (this.cells == var7) {
                                Striped64.Cell[] var34 = new Striped64.Cell[var9 << 1];

                                for(int var35 = 0; var35 < var9; ++var35) {
                                    var34[var35] = var7[var35];
                                }

                                this.cells = var34;
                            }
                        } finally {
                            this.cellsBusy = 0;
                        }

                        var6 = false;
                        continue;
                    }
                } else {
                    var6 = false;
                }
            }
			// 重新计算hash
            var5 = advanceProbe(var5);
        } else if (this.cellsBusy == 0 && this.cells == var7 && this.casCellsBusy()) {  // 初始化 Cell 数组
            boolean var12 = false;

            try {
                if (this.cells == var7) {
                    Striped64.Cell[] var13 = new Striped64.Cell[2];
                    // var5:访问 Cell 数组的那个位置
                    var13[var5 & 1] = new Striped64.Cell(var1);
                    this.cells = var13;
                    var12 = true;
                }
            } finally {
                this.cellsBusy = 0;
            }

            if (var12) {
                break;
            }
        } else if (this.casBase(var10 = this.base, var3 == null ? var10 + var1 : var3.applyAsLong(var10, var1))) {
            break;
        }
    }

}

LongAccumulator

LongAdder 类是 LongAccumulator类的一个特例

private final LongBinaryOperator function;

public void accumulate(long var1) {
    Cell[] var3;
    long var4;
    long var8;
    // 可以让用户自定义类加规则
    if ((var3 = this.cells) != null || (var8 = this.function.applyAsLong(var4 = this.base, var1)) != var4 && !this.casBase(var4, var8)) {
        boolean var12 = true;
        long var6;
        int var10;
        Cell var11;
        if (var3 == null || (var10 = var3.length - 1) < 0 || (var11 = var3[getProbe() & var10]) == null || !(var12 = (var8 = this.function.applyAsLong(var6 = var11.value, var1)) == var6 || var11.cas(var6, var8))) {
            this.longAccumulate(var1, this.function, var12);
        }
    }

}
@FunctionalInterface
public interface LongBinaryOperator {
    long applyAsLong(long var1, long var3);
}

区别

// LongAdder
!this.casBase(var4 = this.base, var4 + var1);
// LongAccumulator
(var8 = this.function.applyAsLong(var4 = this.base, var1)) != var4 && !this.casBase(var4, var8)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值