一、JUC基本概念
1.wait和sleep区别:
wait是Object的方法,会释放锁。(在哪睡在哪醒)
Sleep是Thread的方法,不释放锁。
2.管程(Monitor): 是一种同步机制,保证同一时间,只有一个线程访问被保护数据或代码。管程是随着对象创建而创建的,每个Java对象都有一个管程
jvm同步就是通过每次进入和退出,使用管程对象实现的,每次进入判断管程对象计数器是否为0,为0进入,不为0不进。
3.用户线程:自定义线程, 平时自己new的线程
主线程(main)结束了,用户线程还在运行,jvm存活。
守护线程: 比如垃圾回收,这种运行在后台的线程
主线程结束了,jvm结束。
4.Synchronized:Java中的关键字,是一种同步锁。
- 修饰代码块,作用范围是大括号{}括起来的代码,作用对象是调用代码块的对象
- 修饰方法,被称为同步方法,作用范围是整个方法,作用对象是调用这个方法的对象。
二、多线程编程步骤
step1:
创建资源类,在资源类创建属性与操作方法 。
就是去封装一个可以直接使用功能的类,不需要了解类里面的内容
step2:
在资源类中使用方法
(1)判断
(2)干活
(3)通知
step3:
创建多个线程,调用资源类的才做方法
step4:
防止虚假唤醒问题(判断条件放到while中,要将wait放在循环中,保证在醒来后在进行一次检查)
三、Lock
1.Lock和synchronized区别
- Lock是一个类,synchronized是Java关键字。
- Lock需要手动释放锁。synchronized自动上锁放锁
- 发生异常lock不会自动释放锁,synchronized会
- Lock可以让锁等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待线程会一直等待
- Lock性能高
2.案例
eg:3个售货员,卖30张票
**分析:**资源类是票,一个售货员一个线程
Lock
java
package com.sjy.lock;
import java.util.concurrent.locks.ReentrantLock;
class LTicket{
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
public void sale(){
lock.lock();
try{
if(number>0){
System.out.println(Thread.currentThread().getName()+" 卖出: "+(number--)+"剩余:"+number);
}
}finally {
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(()->{
for(int i = 0;i<100;++i){
ticket.sale();
}
},"aa").start();
new Thread(()->{
for(int i = 0;i<100;++i){
ticket.sale();
}
},"bb").start();
new Thread(()->{
for(int i = 0;i<100;++i){
ticket.sale();
}
},"cc").start();
}
}
synchronized
java
package com.sjy.sync;
class Ticket{
//票数
private int number = 100;
//操作方法:买票
//因为这个方法是多个线程调用,操作同一资源,所以需要加锁保证线程安全
public synchronized void sale(){
//判断:是否有票
if(number > 0){
System.out.println(Thread.currentThread().getName()+" : "+(number--)+"剩下:"+number);
}
}
}
public class SaleTicket {
//创建多个线程调用资源的操作方法
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<100;++i){
ticket.sale();
}
}
},"aa").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<100;++i){
ticket.sale();
}
}
},"bb").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<100;++i){
ticket.sale();
}
}
},"cc").start();
}
}
四、线程间通信定制
指多个线程在运行时,为了共同完成一个任务,彼此之间通过某种媒介发送信号或等待信号。
就是通过资源类里面共享资源,每个线程进行条件判断,就能实现通信。
例如下面这个例子中,flag标志位就是共享资源,建立三个Condition等候区,flag==1时c1的线程执行并改变标注为,flag==2时c2线程执行。
java
package com.sjy.lock;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource {
//定义标志位
private int flag = 1;
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while(flag !=1){
//等待
c1.await();
}
for(int i = 0;i<=5;++i){
System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
}
flag = 2;
//通知
c2.signal();
}finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while(flag !=2){
//等待
c2.await();
}
for(int i = 0;i<=10;++i){
System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
}
flag = 3;
//通知
c3.signal();
}finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while(flag !=3){
//等待
c3.await();
}
for(int i = 0;i<=15;++i){
System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
}
flag = 1;
//通知
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3{
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for(int i = 1;i<=10;++i){
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"aa").start();
new Thread(()->{
for(int i = 1;i<=10;++i){
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"bb").start();
new Thread(()->{
for(int i = 1;i<=10;++i){
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"cc").start();
}
}
五、集合的线程安全
(一)List集合线程安全
1.问题描述
ArrayList 的 add 方法核心逻辑大致如下:
在add方法底层并没有加锁
在 main 方法中,每个线程都在做两件事:
-
写操作 :
list.add(...) -
读操作 :
System.out.println(list)(隐式触发了对整个list的遍历)
当 线程 A 正在执行 System.out.println(list) 遍历到一半时,线程 B 刚好执行了 list.add()。
这样list中的modCount和迭代器中的expectedModCount就不一致了,就会抛出异常
2.解决方案
CopyOnWriteArrayList(写时复制技术)原理:
读的时候并发的读,写的时候独立写。每次写复制一份,在合并集合。这样写的时候就不会影响到读的内容
当你执行 add()、set() 或 remove() 等修改操作时,它不是直接在原数组上改,而是分为以下几步:
-
加锁 :先通过
ReentrantLock加锁,确保同一时刻只有一个线程在修改。 -
克隆 :把当前内部存储数据的数组(
array)完全复制出一份副本。 -
修改:在副本数组上执行添加或删除操作。
-
替换 :将副本数组赋值回内部的
array引用。 -
解锁:释放锁。
我们这里只是解决了线程安全的问题,保证不会抛异常,但是读的时候还是可能会乱序,那不是我们要解决的问题
**使用方法:**直接new
就行,CopyOnWriteArrayList继承List,改写了add操作。
java
package com.sjy.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* list集合线程不安全
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//CopyOnWriteArrayList解决
List<String> list = new CopyOnWriteArrayList<>();
for(int i = 0;i<10;++i){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
(二)HashSet线程安全
解决方案
CopyOnWriteArraySet
用法和List一样
(三)HashMap线程安全
解决方案
CopyOnWriteArraySet
用法和List一样
六、多线程锁
1.synchronized锁的作用范围:
- 对于普通同步方法,锁是当前示例对象
- 对于静态同步方法,锁是当前类的Class字节码对象
- 对于同步代码块,锁是括号里自己设置的对象
2.非公平锁:
谁抢到谁执行。线程饿死,效率高。
实现方法:默认是非公平锁
java
private final ReentrantLock lock = new ReentrantLock(false);
3.公平锁:
线程抢到锁后会判断队列中是否有人排队,有人的话会进入队列排队,依次执行,效率相对较低。
java
private final ReentrantLock lock = new ReentrantLock(true);
.可重入锁:
就是拿了锁,可以进这个锁锁住的所有门。synchronized和Lock都是可重入锁。
5.死锁
两个或两个以上的线程,因为争夺资源而导致的一种互相等待的现象。
例如下面这个例子,线程1抢到了a,线程2抢到了b双方互相等待对方放锁这就是死锁
java
public class DeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+"持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获取锁a");
}
}
},"B").start();
}
}
如何判断程序中有死锁存在:
我们利用jdk提供的命令:
jps -l查找当前正在运行的程序。找到我们要检查的进程ID。
jstack ID会告诉里有没有死锁
bash
jps -l
jstack ID
七、Callable
Runnable和Callable的区别:Callable有返回值,Runnable没
java
class MyThread1 implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable{
@Override
public Object call() throws Exception {
return 200;
}
}
由于Callable没办法直接被Thread使用,所以我们找到了FutrueTask封装了Callable和Runnable
FutrueTask为什么叫这个名字?
就相当于分配出去一个任务让线程做,不要求立马做完,想要得到结果时,随时调用,如果没做完就等待。
java
package com.sjy.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName()+"come in callable");
return 1024;
});
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName()+"come in callable");
return 200;
});
//创建一个线程
new Thread(futureTask2,"lucy").start();
new Thread(futureTask1,"mary").start();
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
}
}
八、JUC辅助类
(一)减少计数CountDownLatch
官方定义:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
给定一个初始值count,每次调用countDown()方法count都会-1,await()方法等待count不大于0.继续执行await后的语句。
- 调用countDown方法的线程不会阻塞
- 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
使用步骤:
-
创建CountDownLatch对象,设置初始值
-
每次操作计数-1
-
在何时地方设置await等待计数器归零
案例:
6个同学陆续离开教室后班长才可以锁门
java
import java.util.concurrent.CountDownLatch;
public class countDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室
for(int i = 0;i<6;++i){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教师");
//计数-1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//当计数器未到0时,一直等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"班长锁门");
}
}
(二)循环栅栏CyclicBarrier
官方定义:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

使用步骤
- 创建目标线程数量
- 设置栅栏任务奖励
- 派遣线程去await打到目标值获得奖励
案例
集齐七颗龙珠
java
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("集齐7颗龙珠召唤神龙");
});
//集齐七颗龙珠过程
for(int i = 0;i<7;++i){
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"星");
cyclicBarrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
},String.valueOf(i)).start();
}
}
}
(三)Semaphore
官方定义: 一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
九、读写锁
缺点:
(1)造成锁饥饿,一直读,没有写操作
(2)读时候,不能写,只有都完成后,才可以写。写操作的时候,可以读
悲观锁:每个线程操作时,其他线程只能阻塞
乐观锁:每个线程同时操作,并设置版本号,更新一次数据升级一次版本号,当线程发现自己的版本号和最新版本号不同,就进行合并,并更新版本号。
表锁:锁住一整张数据库表
行锁:锁住一行,会有死锁
读锁:共享锁,可以多个线程一起读,会有死锁
写锁:独占锁,只能有一个线程进行写,会有死锁
案例:
java
package com.sjy.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value){
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
TimeUnit.MICROSECONDS.sleep(300);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key){
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//创建线程放数据
for(int i = 1 ;i<=5;++i){
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
for(int i = 1 ;i<=5;++i){
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
十、阻塞队列BlockingQueue

当队列为空,从队列获取元素会被阻塞,直到其他线程插入新元素被唤醒
当队列满了,从队列中添加元素会被阻塞,直到其他线程移除一个或多个元素后唤醒
优点:不需要关心什么时候要阻塞线程,什么是皇后要唤醒。
(一)ArrayBlockingQueue(定长的阻塞队列)
由数组结构组成的有界阻塞队列
(二)LinkedBlockingQueue
由链表结构组成的有界(大小默认为integer.MAX_VALUE)阻塞队列

十一、ThreadPool线程池
newFixedThreadPool(int)一池n线程
newSingeThreadExecutor()一池一线程
newCachedThreadPool()根据需求创建线程,可扩容
三种线程池使用方法都一样,只是分配策略不一样。都是通过Executors框架来创建线程池。使用的时候只需要使用线程池的execute方法并指定Runnable任务即可,最后调用shutdown方法,线程池拒绝处理新的任务,并处理剩余任务,释放资源。
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//ExecutorService threadPool1 = Executors.newSingleThreadExecutor();// 5个窗口
//ExecutorService threadPool1 = Executors.newFixedThreadPool(5);// 5个窗口
ExecutorService threadPool1 = Executors.newCachedThreadPool();// 5个窗口
//10个顾客请求
try {
for (int i = 0; i < 10; ++i) {
threadPool1.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
}finally {
threadPool1.shutdown();
}
}
}
底层原理
我们翻看源码会发现每个newThreadPool方法底层都会new一个ThreadPoolExecutor
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

底层执行流程:
当执行了excute()方法后线程才被创建,任务处理会优先使用常驻线程,当常驻线程满了,第3,4,5个任务会在阻塞队列等待,当阻塞队列满了,会创建剩下的线程,给阻塞队列处理,当线程数满了,就会开始执行拒绝策略

自定义线程池(常用)
就按照上述参数,创建一个你需要的线程池。随后execute方法分配任务,
java
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i < 10; ++i) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
