java并发编程JUC
HNU Peter Lv2

1、什么是JUC

image-20220413100650525

回顾:Runnable接口与Callable接口

  1. callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值

  2. callable和runnable都可以应用于executors。而thread类只支持runnable

2、进程和线程

一个进程可以包含多个线程,Java默认线程数:2个, mainGC

对于Java而言,开启线程的方式为:Thread,Runnable,Callable

  • 问题:Java真的可以开启线程吗?

否,我们来看一段代码:

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
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();

/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);

boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}


//本地方法,底层调用C++,Java无法直接硬件操作
private native void start0();

并发,并行

并发:多线程操作同一资源

  • 单CPU,线程间快速切换

并行:

  • 多CPU,多线程同时进行
1
2
3
4
5
6
public class test {
public static void main(String[] args) {
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}

线程有哪几个状态?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum State {
//新生
NEW,

//运行
RUNNABLE,

BLOCKED,

WAITING,

//超时等待
TIMED_WAITING,

TERMINATED;
}

wait/sleep区别

  1. 来自不同类

wait => Object

sleep = > Thread

  1. 锁的释放

wait释放,wait不释放

  1. 使用范围

sleep可以在任何地方使用

wait只能用于同步代码快

  1. 是否捕获异常

wait不需要(==但是所有线程都会捕获中断异常==),而sleep必须捕获

3、Lock锁

  1. 传统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
36
37
38
39
40
package juc;

//多线程模拟售票(并发)
public class ticketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//并发:多线程操作同一个资源类,直接把资源丢进线程

//线程A
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//线程B
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"B").start();
}
}

//资源类:属性和方法OOP
//尽量不要在此处继承Runnable接口,降低耦合度
class Ticket{
//属性
private int num=20;

//方法
public synchronized void sale(){
if(num>0)
System.out.println("剩余票数为" + (num--)+"线程为"+ Thread.currentThread().getName());
}
}
  1. Lock锁
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
package juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//多线程模拟售票(并发)
public class ticketDemo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
//并发:多线程操作同一个资源类,直接把资源丢进线程

//线程A
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();

//线程B
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"B").start();
}
}
//Lock三部曲

/**
* 1.new ReentrantLock()
* 2.lock.lock()加锁
* 3.finally => lock.unlock 解锁
*/
class Ticket2{
//属性
private int num=20;

Lock lock = new ReentrantLock();

//方法
public void sale(){

lock.lock(); //加锁
try { //业务代码
if (num > 0)
System.out.println("剩余票数为" + (num--) + "线程为" + Thread.currentThread().getName());
}catch (Exception e){
e.printStackTrace();
}finally { //无论如何都要解锁
lock.unlock();
}
}
}

==synchronized与lock锁的区别==

  1. synchronized 内置的Java关键字,Lock是一个Java类
  2. synchronized 无法判断锁的状态,lock可以判断是否获得锁
  3. 前者自动释放锁,lock必须手动释放!如果不释放,将导致死锁
  4. synchroniezd无法获得锁将阻塞等待,lock则不会一直等待。
  5. synchronized 可重入锁,不可中断,非公平,而Lock是可重入锁,可以判断锁,默认非公平(可修改)。

可重入锁:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

  1. synchronized适合锁少量的代码同步问题,lock适合锁大量的同步代码。

==锁是什么?如何判断锁是谁?==

4、生产者消费者问题

三个步骤:等待 -> 操作 -> 通知

  • synchronized实现方案
  • lock实现方案

两者的区别是什么?

synchronized通过wait方法使线程阻塞,notify方法唤醒线程

而lock则是通过await方法阻塞等待,signal唤醒,相比lock可支持定向唤醒某个线程(condition)

1
2
3
Condition con1 = lock.newCondition()
Condition con2 = lock.newCondition()
con1.signal(); //指定唤醒

注意:为防止虚假唤醒,在判断时,应当使用==while==

5、八锁现象

涉及到锁的八个问题。(注意同步关系)

注意:在所有的锁中,如果是锁的对象中的某个方法或者属性,则表示对该对象加锁。

而如果是对静态方法加锁,则表示对Class(模板)加锁,所有对静态属性或者静态方法加锁的行为都是同一把锁。

6、CopyOnWriteArrayList(C

  • ArrayList的并发异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;

//list的并发异常ConcurrentModificationException
public class Listdemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();

for (int i = 0; i < 100; i++) {
new Thread(()->{
arrayList.add("hello");
}).start();
}
System.out.println(arrayList);
}
}

Exception in thread “main” java.util.ConcurrentModificationException

==并发异常的解决方案:==

  1. 使用线程安全类Vector
1
List arrayList = new Vector();
  1. 使用工具类Collections
1
List arrayList = Collections.synchronizedList(new ArrayList());
  1. ==CopyOnWriteArrayList(写入时复制)==(读写分离)

多个线程写入的时候,避免覆盖

1
List arrayList = new CopyOnWriteArrayList();

看底层代码:

1
2
3
4
5
6
7
8
9
10
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}

相比Vector不需要在读的时候加锁,写入时复制大大提高读取效率。(即读操作不需要进行同步)l(在开发中读比写频繁)

看一下Vector的get方法如下

1
2
3
4
5
6
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);

return elementData(index);
}

7、CopyOnWriteArraySet

1
Set arraySet = new CopyOnWriteArraySet();

8、ConcurrentHashMap

HashMap在并发下put产生异常!

1
2
3
4
5
6
7
8
9
10
11
      Map map = new HashMap();
//并发HashMap
Map map = new ConcurrentHashMap();
for (int i = 0; i < 50; i++) {
int finalI = i;
new Thread(()->{
map.put(String.valueOf(finalI),"world");
}).start();
}
System.out.println(map);
}

9、JUC常用辅助类

1. CountDownLatch

资源–

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
package juc;

import java.util.concurrent.CountDownLatch;

public class CountDownDemo {
public static void main(String[] args) throws InterruptedException {
//线程计数器
CountDownLatch countDownLatch = new CountDownLatch (6);

for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
countDownLatch.countDown();//计数器-1
//System.out.println(finalI);
}).start();
}

//判断是否归零,接着再执行下一步操作
countDownLatch.await();
System.out.println(countDownLatch.getCount());

System.out.println("close door");

}
}

2. CyclicBarrier

资源++

线程计数器(集齐7颗龙珠触发操作):指定个数线程执行完毕之后再执行操作

3. Semaphere

信号量同一时间只能有指定个线程执行(限流

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
package juc;

import java.util.concurrent.Semaphore;

public class SemaphereDemo {
public static void main(String[] args) {
//线程数量
Semaphore semaphore = new Semaphore(3);

for (int i = 0; i < 5; i++) {
new Thread(()->{
//aquire()得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" running");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" closed");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();

}

}).start();
}
}
}

原理: semaphore.acquire(); 获得,如果已满则等待

​ semaphore.release(); 释放,会将当前信号量+1,然后唤醒等待线程

作用多个共享资源互斥使用,并发限流,控制最大线程数。

10、读写锁ReadWriteLock

image-20220415094542133

11、BlockingQueue阻塞队列(接口)

用处:多线程并发处理,线程池 。

如何使用队列

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add offer put offer
移除 remove poll take poll
检测队首 element peek -
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 juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
* 1.抛出异常
*/
public class BlockDemo {
public static void main(String[] args) {
test1();
}
public static void test1(){

//队列大小
BlockingQueue blockingQueue = new ArrayBlockingQueue(2);


System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
//System.out.println(blockingQueue.add("c"));
//抛出异常IllegalStateException: Queue full

blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
//抛出异常NoSuchElementException

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/**
* 2.有返回值,不抛出异常
*/
public static void test2(){
//队列大小
BlockingQueue blockingQueue = new ArrayBlockingQueue(2);

System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c")); //输出false

System.out.println(blockingQueue.peek()); //输出a

blockingQueue.poll();
blockingQueue.poll();
System.out.println(blockingQueue.poll()); //输出null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
*3.阻塞等待
*/
public static void test3() throws InterruptedException {
//队列大小
BlockingQueue blockingQueue = new ArrayBlockingQueue(2);

blockingQueue.put("a");
blockingQueue.put("a");
//blockingQueue.put("a"); //阻塞等待

System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
blockingQueue.take(); //阻塞
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
*4.超时等待
*/
public static void test4() throws InterruptedException {
//队列大小
BlockingQueue blockingQueue = new ArrayBlockingQueue(2);


blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c",2, TimeUnit.SECONDS); //超时等待2秒

System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2,TimeUnit.SECONDS);
}

12、同步队列SynchronousQueue(类)

同步队列不存储元素,put了一个元素后,必须先take出来,否则无法put。

13、==线程池(重点)==

三大方法,7大参数,4种拒绝策略

池化技术:事先准备好一些资源,有需要则来拿,用完还回去。

程序运行的本质:占用系统资源!优化资源的使用!=>池化技术

线程池、连接池、内存池….

默认大小:2

线程池好处:

  1. 降低资源的消耗
  2. 提高响应速度(不需要创建和销毁等浪费时间的操作)
  3. 方便管理

==线程复用,控制最大并发数,管理线程==

  • 3大方法
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 juc;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//Excutors 工具类、3大方法
public class PoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor(); //单个线程
ExecutorService executorService1 = Executors.newFixedThreadPool(5); //创建一个固定线程池大小
ExecutorService executorService2 = Executors.newCachedThreadPool(); //可伸缩的线程池

//开启线程(使用线程池创建线程)
try {
for (int i = 0; i < 100; i++) {
executorService1.execute(()->{
System.out.println(Thread.currentThread().getName()+"已启动!");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
executorService1.shutdown(); //保证线程池用完关闭
}


}
}

  • 7大参数

先来看看三大方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//方法一:创建单个线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

//方法二:创建固定线程池大小
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

//方法三
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

本质上:上述三种方法底层都是调用==ThreadPoolExecutor==方法

因此,我们来看看ThreadPoolExecutor方法:

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,   //核心线程池大小(当前可用线程)
int maximumPoolSize, //最大线程池大小
long keepAliveTime, //存活时间(线程最大等待时间)
TimeUnit unit, //存活时间单位
BlockingQueue<Runnable> workQueue) { //阻塞队列
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler); //线程工厂和拒绝策略(4种策略)
}

阿里开发守则之并发:

image-20220417135553755

接下来我们看一下==四大拒绝策略==:

1
2
3
4
new ThreadPoolExecutor.AbortPolicy());//队列满了,最大线程为8,此时抛出RejectedExecutionException异常
new ThreadPoolExecutor().DiscardPolicy()); //队列满,丢掉任务,不抛出异常
new ThreadPoolExecutor().DiscardOldestPolicy()); //队列满,尝试和最早的线程竞争,竞争成功执行,失败丢弃不抛出异常
new ThreadPoolExecutor().CallerRunsPolicy()); //哪来的去哪里,由调用线程处理
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
package juc;

import java.util.concurrent.*;

//Excutors 工具类、3大方法
public class PoolDemo {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue(3), //最大承载线程为队列长度+最大线程池
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//队列满了,最大线程为8,此时抛出RejectedExecutionException异常
// new ThreadPoolExecutor().DiscardPolicy()); //队列满,丢掉任务,不抛出异常
//new ThreadPoolExecutor().DiscardOldestPolicy()); //队列满,尝试和最早的线程竞争,竞争成功执行,失败丢弃不抛出异常
//new ThreadPoolExecutor().CallerRunsPolicy()); //哪来的去哪里,由调用线程处理

//开启线程(使用线程池创建线程)
try {
for (int i = 0; i < 9; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"已启动!");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPoolExecutor.shutdown(); //保证线程池用完关闭
}
}
}

==最大的线程池大小maximumPoolSize如何设置?==

CPU密集型与IO密集型

1
2
3
4
5
maximumPoolSize //最大线程池大小
1.CPU密集型,=CPU核数
2.IO密集型,>IO任务线程数

Runtime.getRuntime().availableProcessors(); //获取处理器核数

14、四大函数式接口(必须掌握)(java.util.function)

新时代程序员必会:lambda表达式、链式编程、函数式接口、Stream流式计算

(旧时代程序员:泛型、枚举、反射

函数式接口:只有一个方法的接口

​ 作用:简化编程模型,在新版本的框架底层大量应用

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
  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
package juc;

import java.util.function.Function;

/**
* 1.Function函数式接口
* 只要是函数式接口,都可用lambda表达式简化
*/
public class InterfaceDemo {
public static void main(String[] args) {
Function function = new Function<String,String>(){
@Override
public String apply(String o) {
return o;
}
};
//lambda表达式
Function function1 = str->{
return str;
};
System.out.println(function.apply("hello"));
System.out.println(function1.apply("world"));
}
}

  1. ==断定型接口==
1
2
3
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package juc;

import java.util.function.Predicate;

public class PredictedDemo {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
//用lambda表达式简化
Predicate<String> predicate1 = str->{
return str.isEmpty();
};
System.out.println(predicate.test("hello"));
System.out.println(predicate1.test("hello"));
}
}
  1. ==消费型接口==
1
2
3
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package juc;

import java.util.function.Consumer;
/**
* 3.消费型接口
* 只有一个输入参数
*/
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
//lambda表达式
Consumer<String> consumer1 = str->{
System.out.println(str);
};
consumer.accept("hello");
consumer1.accept("world");
}
}
  1. ==供给型接口==
1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get(); //只有返回值类型,没有输入参数
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package juc;

import java.util.function.Supplier;
/**
* 4.供给型接口
* 没有输出参数,只有返回值
*/
public class SupplierDemo {
public static void main(String[] args) {
Supplier<String> stringSupplier = new Supplier<String>(){
@Override
public String get() {
return "hello";
}
};
//lambda表达式
Supplier<String> supplier = ()->{
return "world";
};
System.out.println(stringSupplier.get());
System.out.println(supplier.get());
}
}

15、JMM

Java内存模型,是不存在的东西,只是概念和约定!

例如:

  • 线程解锁前,必须把共享变量立刻刷回主存。
  • 线程加锁前,必须读取主存中的最新值到工内存。
  • 加锁和解锁是同一把锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package juc;

public class JMMDemo {
private static int num=0;
public static void main(String[] args){
new Thread(()->{
while (num ==0){ //线程永远无法停止

}
}).start();
num=1;
System.out.println(num);
System.out.println(Thread.activeCount());
}
}

image-20220418170329745

16、Volatile

三大特性

  • 可见性
  • 非原子性
  • 禁止指令重排(内存屏障)

17、单例模式

  • 饿汉式
1
2
3
4
5
6
7
8
9
10
//单例模式(饿汉式)
public class Hungry {
//构造方式为私有
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry(); //已经生成了对象(饿汉式)
public static Hungry getInstance(){
return HUNGRY;
}
}
  • DCL懒汉式
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
 package juc.singleDemo;

public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan lazyMan; //为何加volatile?防止指令重排

//双重检测,防止多线程产生问题
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){ //锁住类Class,防止多线程创建对象
if(lazyMan==null){
lazyMan= new LazyMan(); //不是一个原子操作,可能发生指令重排
/**
* 1.分配内存空间
* 2.执行构造方法
* 3.把对象指向该空间
* 123顺序执行没问题,但如果132在第二步时进入第二个线程则会直接返回lazyMan,此时对象还未完成构造
*/
}
}
}
return lazyMan;
}
}

18、CAS(与乐观锁原理相同)

compareAndSet:比较当前工作内存中的值和主内存中的值,如果是期望值那么执行操作否则一直循环。

缺点:

1、循环耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package juc.singleDemo;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemmo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
System.out.println(atomicInteger.get());

System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.compareAndSet(2022, 2023)); //false
System.out.println(atomicInteger.get());

}
}

image-20220418212315812

**==CAS的ABA问题==**:如果改变的是基本类型则没影响,但是如果改变的是引用类型,由于Java是值传递,虽然传递的值不变,但可能引用的对象已经发生改变。

如何解决?原子引用!(带版本号的原子操作)

19、java的Integer比较

  • **==注意==**:==所有相同类型的包装类对象之间的值比较,应该使用equals方法比较。==–来自阿里巴巴java开发手册。

例:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args)throws Exception{

Integer a = -121;
Integer b = -121;
Integer c = 345;
Integer d = 345;
System.out.println(a.equals(b));
System.out.println(a == b);
System.out.println(c.equals(d));
System.out.println(c == d);
}

打印结果为:

1
2
3
4
true
true
true
false

c和d的值都是345,为什么用==和equals比较结果不一样呢?

我们看下对象信息,注意对象地址:

Integer值的比较有个坑:对于Integer var = ?,==在-128至127范围内的赋值, Integer 对象是在IntegerCache.cache 产生,会复用已有对象==,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象;所以,在上面,我们的c和d两个,虽然值是一样的,但是地址不一样。

这是一个大坑,很多人会在项目中使用==来比较Integer!强烈建议,必须使用equals来比较!

20、可重入锁(递归锁)

  • 在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

  • 也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块

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
package juc.Lock;

//可重入锁
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
//线程A
new Thread(()->{
phone.call();
},"A").start();
//线程B
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
send(); //这里也有锁,此处自动获得内层的锁
}
public synchronized void send(){
System.out.println(Thread.currentThread().getName()+"send");
}
}

输出:
Acall
Asend
Bcall
Bsend