JUC
多线程
进程和线程
进程与线程最主要的区别是它们是操作系统管理资源的不同方式的体现。 准确来说进程与线程属于衍生关系。 进程是操作系统执行程序的一次过程,在这个过程中可能会产生多个线程。
比如在使用QQ时,有窗口线程, 文字发送的线程,语音输入的线程,可能不是很恰当,但是就是这个意思。
由于系统在线程之间的切换比在进程之间的切换更高效率,所以线程也被成为轻量级进程。
并发和并行
并发: 多个线程任务被一个或多个cpu轮流执行。 并发强调的是计算机应用程序有处理多个任务的能力。
并行:多个线程被一个或多个cpu同时执行。并行强调的是计算机应用程序拥有同时处理多任务的能力。
线程的几种状态
Thread类源码中的State枚举类
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
Lock多线程代码示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket { //资源类
private int number = 30;
private Lock lock = new ReentrantLock();
void saleTicket() {
//用读写锁替换synchronized
// synchronized (this) {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第[" + number-- + "]张票,还剩[" + number + "]张票");
} else {
System.out.println(Thread.currentThread().getName() + ",票卖完了");
}
} finally {
lock.unlock();
}
// }
}
}
class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable r = () -> {
for (int i = 0; i <= 40; i++) {
ticket.saleTicket();
}
};
Thread t1 = new Thread(r, "A线程");
Thread t2 = new Thread(r, "B线程");
t1.start();
t2.start();
}
}
wait和notifyAll
wait()方法之所以要用while而不是if:
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
代码片段
/**
* 两个线程 可以操作初始值为零的一个变量
* <p>
* 实现一个线程对变量加1 ,一个线程对该变量减1
* 实现交替,来10轮,变量初始值为0
*/
class AirConditioner {
private int number = 0;
void increment() {
synchronized (this) {
// if 虚假唤醒
while (number != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
}
void decrement() {
synchronized (this) {
// if
while (number == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.decrement();
}
}, "D").start();
}
}
Condition
class AirConditioner {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void increment() {
lock.lock();
try {
while (number != 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
void decrement() {
lock.lock();
try {
while (number == 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
airConditioner.decrement();
}
}, "D").start();
}
}
精准唤醒condition
class ShareResource {
private int number = 1;//1.A 2.B 3.C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* 多线程顺序调用 实现A->B->C
* A打印5次,BB打印10次,CC打印15次
*/
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print5();
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print10();
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print15();
}
}, "CC").start();
}
}
List线程安全
public class NoSafeDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + list);
}, i + "").start();
}
}
}
报错 :java.util.ConcurrentModificationException
导致原因:
解决方案使用:
- list = new Vector<>()(线程安全集合,性能差)
- list = Collections.synchronizedList(new ArrayList<>());
- list = new CopyOnWriteArrayList<>(); 读写分离,写时加锁(ReentrantLock),读时没锁
Set线程安全
解决线程安全
- Collections.synchronizedSet(new HashSet<>())
- new CopyOnWriteArraySet<>()
HashSet底层是HashMap
add方法 就是map的put方法 只是value是一个new Object()
CopyOnWriteArraySet底层是CopyOnWriteArrayList
Map线程安全
HashMap的特点
- HashMap在Jdk8之前使用拉链法实现,jdk8之后使用拉链法+红黑树实现。
- HashMap是线程不安全的,并允许null key 和 null value。**
- HashMap在我当前的jdk版本(11)的默认容量为0,在第一次添加元素的时候才初始化容量为 16, 之后才扩容为原来的2倍
- HashMap的扩容是根据 threshold决定的 : threshold = loadFactor * capacity。 当size 大于 threshold 时,扩容。
- 当每个桶的元素数量达到默认的阈值TREEIFY_THRESHOLD(8)时,HashMap会判断当前数组的长度是否大于MIN_TREEIFY_CAPACITY(64),如果大于,那么这个桶的链表将会转为红黑树,否则HashMap将会扩容。 当红黑树节点的数量小于等于默认的阈值UNTREEIFY_THRESHOLD(6)时,那么在扩容的时候,这个桶的红黑树将转为链表。
ConcurrentHashMap
ConcurrentHashMap使用数组+链表/红黑树实现,其扩容机制与HashMap一样。
但是ConcurrentHashMap控制并发的方法改为了CAS+synchronized, synchronized锁的只是链表的首节点或红黑树的首节点。
PS:我只看了常用的put,get,remove等核心方法的源码. 整个ConcurrentHashMap的实现用"复杂"来形容一点也不为过, 你只要想到它内部有52个内部类就知道有多复杂了,但如果不考虑并发CAS这部分, ConcurrentHashMap和普通的HashMap的差别是不大的。
ConcurrentSkipListMap
ConcurrentSkipListMap是基于跳表这种数据结构实现的。 跳表比较特殊,它由多个层次的链表组成,每层链表又有多个索引节点连接, 每层链表的元素也都是有序的。处于上层索引的链表都是下层链表的子集。 跳表与普通链表相比查找元素的效率更高。
BlockingQueue
BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(1);
System.out.println(queue1.add("1"));//true
System.out.println(queue1.add("2"));//java.lang.IllegalStateException: Queue full
BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(1);
System.out.println(queue1.add("1"));//true
String res = queue1.remove();
System.out.println(res);
System.out.println(queue1.remove());//Exception in thread "main" java.util.NoSuchElementException
String element = queue1.element();//队首
String peek = queue1.peek();//队尾
queue1.offer("1",1000,TimeUnit.MILLISECONDS);
Callable
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
};
FutureTask<Integer> task = new FutureTask<>(callable);
new Thread(task).start();
Integer integer = task.get();
System.out.println(integer);
线程池构造参数
new ThreadPoolExecutor
(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize: 线程池的核心线程数(常驻线程数),也就是线程池的最小线程数,这部分线程不会被回收.
-
maximumPoolSize: 线程池最大线程数,线程池中允许同时执行的最大线程数量
-
keepAliveTime: 当线程池中的线程数量超过corePoolSize,但此时没有任务执行,
那么空闲的线程会保持keepAliveTime才会被回收,corePoolSize的线程不会被回收。 -
unit: KeepAliveTime的时间单位
-
workQueue: 当线程池中的线程达到了corePoolSize的线程数量, 并仍然有新任务,那么新任务就会被放入workQueue。
-
threadFactory: 创建工作线程的工厂,也就是如何创建线程的,一般采用默认的
-
handler: 拒绝策略,当线程池中的工作线程达到了最大数量,
并且阻塞队列也已经满了,那么拒绝策略会决定如何处理新的任务。ThreadPoolExecutor 提供了四种策略:
- AbortPolicy**(是线程池的默认拒绝策略):
如果使用此拒绝策略,那么将对新的任务抛出RejectedExecutionException异常,来拒绝任务。 - DiscardPolicy**: 如果使用此策略,那么会拒绝执行新的任务,但不会抛出异常。
- DiscardOldestPolicy**: 如果使用此策略,那么不会拒绝新的任务,但会抛弃阻塞队列中等待最久的那个线程。
- CallerRunsPolicy**: 如果使用此策略,不会拒绝新的任务,但会让调用者执行线程。 也就是说哪个线程发出的任务,哪个线程执行。
newFixedThreadPool和newSingleThreadPoolExecutor都是创建固定线程的线程池, 尽管它们的线程数是固定的,但是它们的阻塞队列的长度却是Integer.MAX_VALUE的,所以, 队列的任务很可能过多,导致OOM。
newCacheThreadPool和newScheduledThreadPool创建出来的线程池的线程数量却是Integer.MAX_VALUE的, 如果任务数量过多,也很可能发生OOM。