多线程

创建和运行线程

Thread类

继承Thread类,重写run方法,或者使用匿名内部类。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void threadTest(){
Thread thread = new Thread(){
@Override
public void run(){
System.out.println("Thread Create");
}
};
thread.setName("t1");
thread.start();
System.out.println("Running");
}

可以通过setName给线程设置名字

Runnable接口

实现Runnable接口,可用匿名内部类,也可使用lambda表达式。

将runnable实现对象传入Thread的构造器中,使用thread开启线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void runnableTest(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Runnable Create!");
}
};

Thread thread = new Thread(runnable);
thread.setName("Runnable");
thread.start();
System.out.println("Running");
}
1
2
3
4
5
6
7
@Test
public void lambdaTest(){
Runnable runnable = () -> System.out.println("Runnable Create");

new Thread(runnable).start();
System.out.println("Running");
}

@FunctionalInterface 函数式接口,可以使用lambda表达式创建其实现对象

Thread和Runnable的关系

使用Runnable时,将runnable对象传入Thread,会将这个runnable对象赋给Thread中的一个target成员变量,当target不为空时,将会调用run重写方法。

使用Thread时,在子类对象重写了run()方法,最终会执行子类中的run方法。

使用FutureTask

FuturenTask实现了RunableFuture接口,RunnableFuture接口实现了Runnable,Future接口。Future接口中有get方法可以返回任务执行结果。

FuturenTask能够接受Callable类型的参数,用来处理有返回结果的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void futureTaskTest() throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("FutureTask Create");
Thread.sleep(2000);
return 100;
}
});

new Thread(futureTask).start();
System.out.println("Running");
System.out.println(futureTask.get());
}

线程运行的原理

栈与栈帧

​ 每个线程启动后,虚拟机就为其分配一块栈内存。每个栈有多个栈帧组成,对应着每次方法调用时所占用的内存,每一个栈帧只能有一个活动栈帧,对应着当前正在执行的那个方法。

线程上下文切换

以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码

  • 线程的CPU时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了sleep,yield、wait、join、park、synchronized、lock等方法

当线程上下文切换发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的就是程序计数器,它的作用是记住下一条JVM指令的执行地址,是线程私有的。

状态包括:程序计数器,栈中每个栈帧的信息;线程上下文切换频繁会影响性能。

常见方法

  • start() 启动一个新线程,在新的线程中运行run方法中的代码;start方法只是让线程进入就绪,具体执行需要任务调度器。每个线程对象的start方法只能调用一次,不能多次调用,否则出现异常
  • run() 新线程启动后就会调用run方法
  • join() 等待线程运行结束(一直等待线程结束)
  • join(long n) 等待线程运行结束,最多等待n毫秒
  • getId() 获取线程唯一id
  • getName() 获取线程名称
  • setName(String) 修改线程名称
  • getPriority() 获取线程优先级
  • setPriority(int) 修改线程优先级,Java中1~10
  • getState() 获取线程状态 Java中有6个线程的状态
  • isInterrupted() 判断是否被打断 不会清除打断标记
  • isAlive() 线程是否存活
  • interrupt() 打断线程
  • interrupted() static 判断当前线程是否被打断 会清除打断标记
  • currentThread() static 获取当前正在执行的线程
  • sleep(long n) static 让当前执行的线程休眠b毫秒,休眠时让出cpu的时间片给其他线程
  • yield() static 提示线程调度器让出当前线程对CPU的使用

start和run

若直接调用对象的run方法,则仍然是在主线程中运行。

sleep和yield

sleep

  • 调用sleep会让线程从Running进入TimedWaiting状态
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出一个异常InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void sleepTest(){
final Thread t1 = new Thread(() -> {
try {
System.out.println("Running");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");

t1.start();
System.out.println(t1.getState());
try {
Thread.sleep(500);
System.out.println(t1.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
Running
RUNNABLE
TIMED_WAITING
*/

interrupt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void interruptTest(){
Thread t1 = new Thread(() -> {
System.out.println("sleep");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("wake up");
e.printStackTrace();
}
});

t1.start();
try {
Thread.sleep(1000);
System.out.println("interrupt");
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//sleep
//interrupt
//wake up

TimeUnit内部也是调用sleep方法,做了单位的换算

1
2
3
4
5
6
7
8
9
10
@Test
public void timeUnitTest(){
System.out.println("enter");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}

yield

  • 调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其他线程
  • 具体实现依赖于操作系统的任务调度器

线程优先级

  • 线程优先级会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但CPU闲时,优先级几乎没作用

案例 防止CPU占用100%

在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序

1
2
3
4
5
6
7
8
while(true)
{
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
}
  • 可以用wait或条件变量达到类似的效果
  • 不同的是,后两种需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep适用于无需锁同步的场景

join 方法详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test{
static int r = 0;
public static void main(String[] args)throws InterruptedException{
test1();
}
private static void test1() throws InterruptedException{
log.debug("开始");
Thread t1 = new Thread(() ->{
log.debug("开始");
sleep(1);
log.debug("结束");
r= 10;
});

t1.start();
log.debug("结束为:{}",r);
log.debug("结束");
}
}

上述代码打印r是什么?r=0

分析:因为主线程和线程1是并行执行的,t1线程需要1秒之后才能算出r=10;而主线程一开始就要打印r的结果,所以只能打印出r=0

解决方法:

  • 用sleep行不行?为什么 不知道到底sleep多久
  • 用join,加在t1.start()之后即可

应用之同步

以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

有时效的join

join(long n)会等待n毫秒,线程提前结束,则直接不等待。

interrupt方法详解

打断sleep、wait、join进程

打断阻塞状态的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void interrupt1Test(){
Thread t1 = new Thread(() -> {
System.out.println("sleep");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("被打断了");
e.printStackTrace();
}
});

t1.start();
try {
Thread.sleep(500);
t1.interrupt();
System.out.println(t1.isInterrupted());
} catch (InterruptedException e) {
e.printStackTrace();
}
}

对于sleep、wait、join被打断后会被清除打断,它们以异常的形式来展示被打断了。

打断正常运行的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InterruptNormalTest {

public static void main(String[] args) throws InterruptedException {
final Thread t1 = new Thread(() -> {
while (true) {

}
});
t1.start();

Thread.sleep(1000);
t1.interrupt();
System.out.println(t1.isInterrupted());
}
}

这样无法打断正常执行的程序,需要在其中的线程中添加判断条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InterruptNormalTest {

public static void main(String[] args) throws InterruptedException {
final Thread t1 = new Thread(() -> {
while (true) {
if(Thread.currentThread().isInterrupted()){
break;
}
}
});
t1.start();

Thread.sleep(1000);
t1.interrupt();
System.out.println(t1.isInterrupted());
}
}

两阶段终止模式

在一个线程T1优雅的终止线程T2,这里的优雅并不是直接杀死,而是给T2一个终止的准备,最后终止。

错误思路:使用线程对象的stop方法停止线程;stop方法杀死线程后,如果这时线程锁住了共享资源,那么就无法释放锁,其他线程永远也没有机会释放锁。使用System.exit方法停止线程:将会把整个进程都停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/27 14:35
*/
public class TwoPhase {

private Thread monitor;

public void start(){
monitor = new Thread(() -> {
while (true){
Thread current = Thread.currentThread();
if (current.isInterrupted()){
System.out.println("准备结束操作!");
break;
}
try {
Thread.sleep(1000);
System.out.println("执行监控操作");
} catch (InterruptedException e) {
e.printStackTrace();
current.interrupt();
}
}
});
monitor.start();
}

public void stop(){
if (monitor != null){
monitor.interrupt();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/27 14:42
*/
public class TwoPhaseTest {

public static void main(String[] args) throws InterruptedException {
TwoPhase twoPhase = new TwoPhase();
twoPhase.start();

Thread.sleep(3500);
twoPhase.stop();
}
}

打断park线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ParkTest {

public static void main(String[] args) throws InterruptedException {
park();

}

private static void park() throws InterruptedException {
final Thread t1 = new Thread(() -> {
System.out.println("park");
LockSupport.park();
System.out.println("unpark");
System.out.println(Thread.currentThread().isInterrupted());
});
t1.start();


Thread.sleep(500);
t1.interrupt();
}
}

若线程中的打断标记已经是true,则park会失效。

不推荐的方法

  • stop() 停止线程运行
  • suspend() 挂起线程运行
  • resume() 恢复线程运行

主线程和守护线程

默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即时守护线程的代码没有执行完,也会强制结束。

垃圾回收器就是一种守护线程;Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/28 8:46
*/
public class DaemonTest {

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
break;
}
}
});
t1.setDaemon(true);
t1.start();

Thread.sleep(1000);
System.out.println("结束!");
}
}

线程的状态

从操作系统层面上分:

  • 初始状态:仅在语言层面创建了线程对象,还未与操作系统线程关联
  • 可运行状态:线程已被创建,可以由CPU调度执行
  • 运行状态:指获取CPU时间片运行中的状态
  • 阻塞状态:调用了阻塞API,如BIO读写文件、这时该线程实际不会用到CPU,会导致线程上下文切换,进入阻塞状态
    • 需要操作系统唤醒阻塞的线程,转至可运行状态
  • 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

从Java API中Thread.State枚举,分为六种状态:

  • NEW 线程刚被创建出来,还没有start
  • RUNNABLE 当调用start()方法之后,Java API 层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】、【阻塞状态】
  • BLOCKED、WAITING、TIMED_WAITING都是Java API层面对阻塞状态的细节。
  • TERMINATED 当线程代码运行结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/28 12:18
*/
public class StateTest {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("running");
});

Thread t2 = new Thread(() -> {
while (true){

}
});
t2.start();

Thread t3 = new Thread(() -> {
System.out.println("running");
});
t3.start();

Thread t4 = new Thread(() -> {
synchronized (StateTest.class){
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t4.start();

Thread t5 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t5.start();

Thread t6 = new Thread(() -> {
synchronized (StateTest.class){
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t6.start();

try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(t1.getState());
System.out.println(t2.getState());
System.out.println(t3.getState());
System.out.println(t4.getState());
System.out.println(t5.getState());
System.out.println(t6.getState());
}
}
/**
running
NEW
RUNNABLE
TERMINATED
TIMED_WAITING
WAITING
BLOCKED
*/

共享带来的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/28 12:44
*/
public class EnjoyProbleam {
public static int counter = 0;

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++){
counter++;
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++){
counter--;
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println(counter);
}
}

问题分析:

以上的结果可能是正数、负数、零。自增、自减操作并不是原子操作。多个线程在上下文切换中,因为指令交错而导致线程安全问题。

临界区

  • 一个程序运行多个线程本身是没有问题的
  • 问题出现在多个线程访问共享资源
    • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

静态条件

多个线程在临界区内执行,由于代码得执行序列不同而导致结果无法预测,称之为发生了竞态条件

synchronized解决方案

为避免临界区的竞态条件发生,有多种手段可以达到目的

  • 阻塞式解决方案:synchronized、Lock
  • 非阻塞式的解决方案:原子变量

synchronized俗称对象锁,它采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其他线程再想获取这个对象锁时就会阻塞。这样就能保证拥有锁的线程可以安全的执行临界区的代码,不用担心线程上下文切换

1
2
3
4
synchronized(对象)
{
临界区
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/6/28 12:44
*/
public class EnjoyProbleam {
public static int counter = 0;
public static Object lock = new Object();

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++){
synchronized (lock) {
counter++;
}
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++){
synchronized (lock) {
counter--;
}
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println(counter);
}
}

思考:

synchronized实际上是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断的

  • 如果把synchronized(obj)放在for循环的外面,如何理解?–原子性
  • 如果把t1 synchronized(obj1)而t2 synchronized(obj2)会怎么运作?–锁对象
  • 如果t1 synchronized(obj)而t2没有加会怎么样?如何理解?–锁对象

把需要保护的共享变量放入一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package synchronizedTest;

public class Room
{
private int counter;

public Room(){}

public void increment()
{
synchronized (this)
{
counter++;
}
}

public void decrement()
{
synchronized (this)
{
counter--;
}
}

public int getCounter()
{
synchronized (this)
{
return counter;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package synchronizedTest;

public class TestRoom
{
public static void main(String[] args) throws InterruptedException{
Room room = new Room();

Thread t1 = new Thread(() ->{
for (int i = 0; i < 5000; i++)
room.increment();
},"t1");

Thread t2 = new Thread(() ->{
for (int i = 0; i < 5000; i++)
room.decrement();
},"t2");

t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getCounter());
}
}

synchronized加在方法上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test
{
public synchronized void test(){

}
}
//等价于
class Test{
public void test(){
synchronized(this){

}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test
{
public synchronized static void test(){

}
}
//等价于
class Test{
public static void test(){
synchronized(Test.class){

}
}
}

线程八锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//情况1
class Number
{
public synchronized void a()
{
System.out.print("1");
}

public synchronized void b()
{
System.out.print("2");
}
}

public class test
{
public static void main(String[] args)
{
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b();}).start();
}
}

//结果为:12或21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//情况2
class Number
{
public synchronized void a()
{
try{
sleep(1000);
System.out.print("1");
}catch
{}
}

public synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b();}).start();
}
}

//结果为:1s后12或2 1s后1(有互斥)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//情况3
class Number
{
public synchronized void a()
{
try{
sleep(1000);
System.out.print("1");
}catch
{}
}

public synchronized void b()
{
System.out.print("2");
}

public void c()
{
System.out.print("3");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b();}).start();
new Thread(()->{ n1.c();}).start();
}
}

//结果为:3 1s后12、23 1s后1、32 1s后1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//情况4
class Number
{
public synchronized void a()
{
sleep(1000);
System.out.print("1");
}

public synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b();}).start();
}
}
//结果为:2 1s后1(无互斥)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//情况5
class Number
{
public static synchronized void a()
{
sleep(1000);
System.out.print("1");
}

public synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b();}).start();
}
}
//结果为:2 1s后1(无互斥)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//情况6
class Number
{
public static synchronized void a()
{
sleep(1000);
System.out.print("1");
}

public static synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b();}).start();
}
}
//结果为:1s后12或2 1s后1(有互斥)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//情况7
class Number
{
public static synchronized void a()
{
sleep(1000);
System.out.print("1");
}

public static synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b();}).start();
}
}
//结果为:2 1s后1(无互斥)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//情况8
class Number
{
public static synchronized void a()
{
sleep(1000);
System.out.print("1");
}

public static synchronized void b()
{
System.out.print("2");
}
}

class test
{
public static void main(String[] args)
{
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b();}).start();
}
}
//结果为:1s后12或2 1s后1(有互斥)

变量的线程安全分析

成员变量和静态变量是否线程安全

  • 如果他们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够被改变,又分两种情况
    • 如果只有读取操作,则线程安全
    • 如果有读有写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全
1
2
3
4
5
public static void test1()
{
int i = 10;
i++;
}

每个线程调用test1()方法时局部变量u,会在每个线程的栈帧内存中被创建多份,因此不存在共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ThreadUnsafe
{
Arraylist<String> list = new ArrayList<>();
public void method1(int loopNumber)
{
for(int i = 0; i < loopNumber; i++)
{
mehtod2();
method3();
}
}

private void method2()
{
list.add("1");
}

private void method3()
{
list.remove(0);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args)
{
ThreadUnsafe test = new ThreadUnsafe();
for(int i = 0; i < THREAD_NUMBER; i++)
{
new Thread(() -> {
test.method1(LOOP_NUMBER);
},"Thread" + i).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ThreadSafe()
{
public final void method1(int loopNumber)
{
Arraylist<String> list = new ArrayList<>();
for(int i = 0; i < loopNumber; i++)
{
mehtod2(list);
method3(list);
}
}

private void method2(ArrayList<String> list)
{
list.add("1");
}

private void method3(ArrayList<String> list)
{
list.remove(0);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ThreadSafe()
{
public final void method1(int loopNumber)
{
Arraylist<String> list = new ArrayList<>();
for(int i = 0; i < loopNumber; i++)
{
mehtod2(list);
method3(list);
}
}

public void method2(ArrayList<String> list)
{
list.add("1");
}

public void method3(ArrayList<String> list)
{
list.remove(0);
}
}

class ThreadSfeSubclass extends ThreadSafe()
{
@Override
public void method3(ArrayList<String> lsit)
{
new Thread(() -> {
list.remove(0);
}).start();
}
}

线程安全类

String、Integer、StringBuffer、Random、Vector、Hashtable、java.util.concurrent包下的类。

1
2
3
4
5
6
7
8
9
10
Hashtable table = new Hashtable();

new Thread(() ->
{
table.put("key","value");
}).start();
new Thread(() ->
{
table.put("key","value");
}).start();
  • 它们的每个方法是原子的
  • 但注意它们多个方法的组合不是原子的
1
2
3
4
5
6
Hashtable table = new Hashtable();
//线程1、线程2
if(table.get("key") == null)
{
table.put("key",value);
}

不安全的

不可变类线程安全性

String、Integer等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//例1
public class MyServlet extends HttpServlet
{
//是否安全?
Map<String,Object> map = new HashMap<>();//不安全,Hashtable才是安全的
//是否安全?
String S1 = "...";//安全的
//是否安全?
final String S2 = "...";//安全的
//是否安全?
Date D1 = new Date();//不安全的
//是否安全?
final Date D2 = new Date();//不安全的

public void doGet(HttpServletRequest request, HttpServletRequest response)
{
//使用上述变量
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//例2
public class MyServlet extends HttpServlet
{
//是否安全?
private UserService userService = new UserServiceImpl();//不安全

public void doGet(HttpServletRequest request, HttpServletRequest response)
{
userService.update(...);
}
}

public class UserServiceImpl implements UserService
{
private int count = 0;

public void update()
{
//...
count++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyAspect
{
//是否安全?
private long start = 0L;

@Before("execution(**(..))")
public void before()
{
start = System.nanoTime();
}

@After("execution(**(..))")
public void after()
{
long end = System.nanoTime();
System.out.println("cost time" + (end - start));
}
}

Monitor概念

Java对象头

普通对象

  • Object Header
    • Mark Word
      • hashcode | age | biased_lock(偏向锁) :0 | 01(加锁状态) Normal(状态)
      • thread
    • Klass Word

数组对象

  • Object Header
    • Mark Word
    • Klcass Word
    • array length

Monitor(锁)

Monitor:监视器或管程

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁之后,该对象的Mark Word中就被设置指向Monitor对象的指针

  • 刚开始Monitor中的Owner为nul
  • 当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
  • 在Thread-2上锁的过程中,如果Thread-3,Thread-4、Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED
  • Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时是非常公平的
  • WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的进程

注意:

  • synchronized必须是进入同一个对象的monitor才有上述的效果
  • 不加synchronized的对象不会关联监视器,不遵从以上规则

字节码角度

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是synchronized。

  • 创建锁记录对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
  • 让锁记录中的Object reference指向锁对象,并尝试用cas替换Object的Mark Word
  • 如果cas替换成功,对象头中存储了锁记录地址和状态00,表示由该线程给对象加锁
  • 如果cas失败,有两种
    • 如果是其它线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record
  • 当退出synchronized代码块锁记录的值为null,表示有重入,这时重置锁记录,表示重入计数减一
  • 当退出synchronized代码块锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级解锁流程。

锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

  • 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁
  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程
    • 即为Object对象申请Monitor锁,让Object指向重量级锁的地址
    • 然后自己进入Monitor的EntryList的BLOCKED
  • 当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给头对象,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程

自旋优化

重量级锁竞争的时候,还可以使用自选来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放锁),这时当前线程就可以避免阻塞。
自旋重试成功的情况

  • 在Java6之后自旋锁是自适应的
  • 自旋锁占用CPU时间,单核CPU自旋就是浪费,多核自旋才能发挥优势
  • Java7之后不能控制是否开启自旋功能

偏向锁

轻量级锁在没有竞争时,每次重入仍然需要执行CAS操作

Java6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不同重新CAS。以后只要不发生竞争,这个对象就归该线程所有

偏向状态

对象头格式

Mark Word State
biased_lock:0 \ 01 Normal
biased_lock:1 \ 01 Biased
\ 00 Lightweight Locked
\ 10 Heavyweight Locked

一个对象创建时:

  • 如果开启了偏向锁,那么对象创建后,markword值为0x05即最后三位是101,它的thread、epoch、age都为0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以添加vm参数-XX:BiasedLockingStartupDelay = 0来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcoe时才会赋值

-xx:-UseBiasedLocking禁用偏向锁

撤销-调用对象 hashCode

调用了对象的hashCode,但偏向锁的对象MarkWord中存储的线程id,如果调用hashCode会导致偏向锁被撤销。

  • 轻量级锁会在锁记录中记录hashcode
  • 重量级锁会在Monitor中记录hashcode

撤销-其他线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

批量重偏向

如果对象被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍然有机会重新偏向T2,重偏向会重置对象的Thread ID

当撤销偏向锁阈值超过20次后,JVM会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至枷锁线程

批量撤销

当撤销偏向锁超过40次后,JVM会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向,新建的对象也是不可偏向的。

锁消除

wait/notify原理

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
  • BLOCKED线程会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍然进入EntryList重新竞争

API介绍

  • obj.wait()让进入object监视器的线程到waitSet等待
  • obj.notify()在object上正在waitSet等待的线程中挑一个唤醒
  • obj.notifyAll()让object上正在waitSet等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于Object对象的方法。必须获得此对象的锁,才能调用这几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/7/7 16:23
*/
public class WaitNotifyTest {
static Object obj = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
System.out.println("执行t1...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他代码t1");
}
});

t1.start();

new Thread(() -> {
synchronized (obj) {
System.out.println("执行t2...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他代码t2");
}
}).start();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒obj其他线程");
synchronized (obj){
obj.notifyAll();
}
}
}
/**
执行t1...
执行t2...
唤醒obj其他线程
其他代码t2
其他代码t1
*/
  • wait(long n)有时限的等待,到n毫秒结束等待或是被notify

  • wait(long timeout, int nanos) 假的纳秒,让timeout+1

wait VS sleep

  • sleep是Thread静态方法,wait是Object的方法
  • sleep不需要强制和synchronized配合使用;但wait需要和synchronized一起用
  • sleep在睡眠的同时,不会释放对象锁,但wait在等待的时候会释放对象锁
  • 它们的状态都是TIMEWATING
1
2
3
4
5
6
7
8
9
10
11
synchronized(look){
while(条件不成立){
lock.wait();
}
//干活
}

//另一个线程
synchronized(look){
lock.notifyAll();
}

同步模式之保护性暂停

用一个线程等待另一个线程的执行结果

要点:

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列
  • JDK中,join的实现,Future的实现,采用的就是此模式
  • 因为要等待另一方的结果,因此归类于同步模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/7/7 17:22
*/
public class GuardedObject {

private Object response;

/**
* 获取结果
* @return result
*/
public Object getResponse(){
synchronized (this){
while (response == null){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

return response;
}
}

/**
* 产生结果
* @param response result
*/
public void setResponse(Object response){
synchronized (this){
this.response = response;
this.notifyAll();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.xiaohupao.juc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: xiaohupao
* @Date: 2021/7/7 17:26
*/
public class Downloader {

public static List<String> download() throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL("https://www.baidu.com").openConnection();
List<String> list = new ArrayList<>();
try(BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))){
String line;
while ((line = reader.readLine()) != null){
list.add(line);
}
}

return list;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cn.xiaohupao.juc;

import java.io.IOException;
import java.util.List;

/**
* @Author: xiaohupao
* @Date: 2021/7/7 17:32
*/
public class GuardedTest {

public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(() ->{
System.out.println("等待结果");
final List<String> response = (List<String>) guardedObject.getResponse();
System.out.println(response.size());
}).start();

new Thread(() -> {
System.out.println("执行下载");
try {
guardedObject.setResponse(Downloader.download());
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}

保护暂停-增加超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object getResponse(long timeOut){
synchronized (this){
long beginTime = System.currentTimeMillis();
long passTime = 0L;
while (response == null){
long waitTime = timeOut - passTime;
if (waitTime <= 0){
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passTime = System.currentTimeMillis() - beginTime;
}

return response;
}
}

join原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

//参数的有效性检查
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

//一直等待
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

Park & Unpark

是LockSupport类中的方法

1
2
3
LockSupport.park();//暂停当前线程

LockSupport.unpark(暂停线程对象);//恢复某个线程的运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cn.xiaohupao.juc;

import java.util.concurrent.locks.LockSupport;

/**
* @Author: xiaohupao
* @Date: 2021/6/28 8:19
*/
public class ParkTest {

public static void main(String[] args) throws InterruptedException {
park();

}

private static void park() throws InterruptedException {
final Thread t1 = new Thread(() -> {
System.out.println("park");
LockSupport.park();
System.out.println("unpark");
System.out.println(Thread.currentThread().isInterrupted());
});
t1.start();


Thread.sleep(10000);
LockSupport.unpark(t1);
}
}

与Object中的wait和notify相比:

  • wait,notify和notifyAll必须配合Object Monitor一起使用,而unpark不必
  • park & unpark是以线程为单位来【阻塞】和【唤醒】线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
  • park & unpark可以先unpark,而wait & notify不能先notify

park & unpark原理

每个线程都有自己的一个Parker对象,由三部分组成_counter, _cond和_mutex。

  • 调用park就是要看需不需要停下来歇息
    • 如果备用干粮耗尽,那么就休息
    • 如果备用干粮充足,那么就继续前进
  • 调用unpark,就好比令干粮充足
    • 如果线程在休息,就唤醒它继续前进
    • 如果这时线程还在运行,那么下次他调用park时,消耗掉备用干粮,不需要休息继续前进
    • 多次调用unpark仅会补充一份备用干粮

线程状态转换

NEW ——> RUNNABLE

当调用t.start()方法时,由NEW - - - > RUNNABLE

RUNNABLE ———> WAITING

t线程用synchronized(obj)获取了对象锁后;

  • 调用了obj.wait()方法时,t线程从runable- - -> waiting
  • 调用obj.notify(),obj.notifyAll(),t.interrupt()时
    • 竞争成功,t线程从waiting - - - > runnable
    • 竞争锁失败,t线程从waiting - - - > blocked
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package cn.xiaohupao.juc;

/**
* @Author: xiaohupao
* @Date: 2021/7/7 16:23
*/
public class WaitNotifyTest {
static Object obj = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
System.out.println("执行t1...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他代码t1");
}
});

t1.start();

new Thread(() -> {
synchronized (obj) {
System.out.println("执行t2...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他代码t2");
}
}).start();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒obj其他线程");
synchronized (obj){
obj.notifyAll();
}
}
}
  • 当前线程调用t.join()方法时,当前线程从runnable - - - > waiting
    • 注意是当前线程在t线程对象的监视器上等待
  • t线程运行结束,或调用了当前线程的interrupt(),当前线程从waiting - - -> runnable

当前线程调用LockSupport.park()方法会让当前线程从runnable - - -> waiting
调用LockSupport.unpark或调用了线程的interrupt(),会让目标线程从 waiting - - > runnable

RUNNABLE ——> TIME_WAITING

t线程用synchronized(obj)获取了对象锁后:

  • 调用obj.wait(long n)方法时,t线程从runnable - - - > time_waiting
  • t线程等待时间超过了n毫秒,或调用obj.notify(),obj.notifyAll(), t.interrupt()时
    • 竞争锁成功,t线程从time_waiting - - - > runnable
    • 竞争锁失败,t线程从time_waiting - - - > blocked

当前线程调用t.join(long n)方法时,当前线程从runnable - - - > time_waiting
当前线程时间超过了n毫秒,或t线程运行结束,或调用了当前线程的interrupt()时,当前线程从time_waiting - - - > runnable

当前线程调用了Thread.sleep(long n),当前线程从runnable - - - > time_waiting
当前线程等待时间超过了n毫秒,当前线程从time_waiting - - - > runnable

当前线程调用LockSupport.parkNanos(long nanos)或LockSupport.parkUntil(long millis)时,当前线程从Runnable - - - > time_waiting
当调用LockSupport.unpark(目标线程)或调用了线程的interrupt()或是等待超时,会让目标线程从time_waiting - - -> runnable

RUNNABLE < - - - > BLOCKED

线程用synchronized(obj)获取了对象锁如果竞争失败,从runnable - - - > blocked

持obj锁线程的同步代码块执行完毕,会唤醒该对象上所有blocked的线程重新竞争,如果其中t线程竞争成功,从block - - - > runnable ,其它失败的线程仍然blocked

RUNNABLE < - - > TERMINATED

当前线程所有代码运行完毕,进入TERMINATED

线程安全集合类概述

遗留的安全集合

  • Hashtable
  • Vector

修饰的安全集合类

Collections修饰的线程安全集合

JUC安全集合

  • Blocking类
    • 大部分实现基于锁,并提供用来阻塞的方法
  • CopyOnWrite类
    • 写|修改的开销相对较重
  • Concurrent类
    • 内部很多操作使用cas优化,可以提高吞吐量
    • 弱一致性
      • 遍历时弱一致性,如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
      • 求集合大小也不一定正确
      • 读取时弱一致性

concurrentHashMap

HashMap并发死链

get流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//保证返回的hash码为正数
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//如果头结点已经是要查找的key
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//头结点为负数,bin在扩容中或是treebin,这时调用find方法来查找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//遍历链表
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}

put方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
//获取一个正数的hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
//cas
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//添加链表头使用了cas
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//帮忙扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//锁住链表头结点
synchronized (f) {
if (tabAt(tab, i) == f) {
//链表
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//增加size计数
addCount(1L, binCount);
return null;
}

数组初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
Donate comment here