Java 多线程——JUC
1、相关基础概念
- 进程:
- 线程:
- 管程:又叫
monitor在Java中就是所说的锁。是一种同步机制,保证同一个时间,只有一个线程能够访问或者代码。 - JVM 同步基于进入合退出,使用管程对象实现
- 用户线程:平时所用到的线程基本上都是的,比如说
new Thread()出来的。自定义线程。 - 守护线程:用于后台中的一种特殊的线程,比如说垃圾回收。
public class Main {
public static void main(String[] args) {
Thread aa = new Thread(() -> {
// isDaemon 表明是用户线程还是守护线程
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while (true) {
}
}, "aa");
// 设置守护线程
aa.setDaemon(true); // 代码A
aa.start();
System.out.println(Thread.currentThread().getName() + "over");
}
}
上面的代码的现象是:主线程结束了,但是用户线程还没有结束,jvm 存活。如果增加上代码A,则没有用户线程,都是守护线程,jvm 结束.
2、 Synchronized 关键字
synchronized 是 Java 中的关键字,是一种同步锁。可以修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块成为同步语句块,其作用范围是大括号
{}括起来的代码,作用的对象是调用这个代码块的对象; - 修饰一个方法,被修饰的方法成为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 虽然可以使用
synchronized来定义方法,但是其并不属于方法定义的一部分,因此,synchronized关键字不能被继承。
- 虽然可以使用
- 修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用范围是**
synchronized后面括号括起来的部分**,作用对象是这个类的所有对象;
// 3 个售票员,卖出 30 张票
class Ticket {
// 票数
private int number = 30;
// 操作方法:卖票
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(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "CC").start();
}
}
2.1 多线程编程的步骤
- 第一步:创建资源类,在资源类创建属性和操作方法;
- 第二步:在资源类的操作方法中:
- 判断
- 干活
- 通知
- 第三步:创建多个线程,调用资源类的操作方法;
- 第四步:防止虚假唤醒问题
3. Lock 接口
3.1 可重入锁:ReentrantLock
reentrantLock 与 synchronized 区别
- Lock 不是 java 语言内置的,synchronized 是内置的;
- Lock 是一个类,通过这个类可以实现同步访问;
4、 创建多线程的多种方式
- 继承 Thread 类
- 实现 Runnable 接口
- 使用 Callable 接口
- 使用线程池
5、线程间的定制通信
// 第一步:创建资源类
class ShareResource {
// 定义标志位
private int flag = 1; // 1 AA; 2 BB; 3 CC
private final ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
// 打印 5 次,参数第几轮
public void printFive(int loop) throws InterruptedException {
lock.lock();
try {
// 判断
while (flag != 1) {
c1.await();
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " :: " + loop);
}
// 通知
flag = 2; // 修改标志位
c2.signal(); // 通知 BB
} finally {
lock.unlock();
}
}
// 打印 10 次,参数第几轮
public void printTen(int loop) throws InterruptedException {
lock.lock();
try {
// 判断
while (flag != 2) {
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " :: " + loop);
}
// 通知
flag = 3; // 修改标志位
c3.signal(); // 通知 BB
} finally {
lock.unlock();
}
}
// 打印 15 次,参数第几轮
public void printFifty(int loop) throws InterruptedException {
lock.lock();
try {
// 判断
while (flag != 3) {
c3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " :: " + loop);
}
// 通知
flag = 1; // 修改标志位
c1.signal(); // 通知 BB
} finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource sResource = new ShareResource();
new Thread(() -> {
for (int i = 0; i <= 10; i++) {
try {
sResource.printFive(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i <= 10; i++) {
try {
sResource.printTen(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
sResource.printFifty(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
}
}
6、集合的线程安全
6.1 ArrayList 线程不安全
- Vector
- Collections.synchronizedList()
- CopyOnWriteArrayList, 写时复制技术
6.2 HashSet 线程不安全
- CopyOnWriteArraySet
6.3 HashMap 线程不安全
- ConcurrentHashMap
7. 八种多线程锁
synchronized实现同步的基础:Java 中的每一个对象都可以作为锁。 具体表现为以下 3 种形式:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 Class 对象。
- 对于同步方法块,锁是 synchronized 括号里面配置的对象。
class Phone {
public synchronized void sendSMS() throws Exception {
// 停留 4 秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------- sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------- sendEmail");
}
public void getHello() {
System.out.println("------- getHello");
}
}
public class ThreadDemo5 {
public static void main(String[] args) throws Exception {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone1.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100); // 确保两个线程都创建
new Thread(() -> {
try {
// phone1.sendEmail();
// phone1.getHello();
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
- 标准访问,先打印短信还是邮件
------- sendSMS
------- sendEmail
当 synchronized 修饰实例方法的时候,锁的是当前的对象,即是 this。
- 停 4 秒在短信方法内,先打印短信还是邮件
------- sendSMS
------- sendEmail
- 新增普通的 hello 方法,先打印短信还是 hello
------- getHello
------- sendSMS
getHello 与锁无关,所以先执行。
- 现在有两部手机,先打印短信还是邮件
------- sendEmail
------- sendSMS
两个 phone 对象用的不是同一把锁。
- 两个静态同步方法,1 部手机,先打印短信还是邮件
------- sendSMS
------- sendEmail
synchronized 的锁对象是当前类的 Class 对象。
- 两个静态同步方法,2 部手机,先打印短信还是邮件
------- sendSMS
------- sendEmail
- 1 个静态同步方法,1 个普通同步方法,1 部手机,先打印短信还是邮件
------- sendEmail
------- sendSMS
此时一个的锁是对象实例 this,一个是类的 Class 对象。下同
- 1 个静态同步方法,1个普通同步方法,2 部手机,先打印短信还是邮件
------- sendEmail
------- sendSMS
8. 公平锁与非公平锁
- 非公平锁可能出现线程空闲的情况;但是效率高
- 公平锁:效率相对较低;
9. 可重入锁(递归锁)
synchronized(隐式)和Lock(显式)都是可重入锁
示例:
public class SyncLockDemo {
public synchronized void add() {
add();
}
public static void main(String[] args) {
/* 会导致栈溢出,因为循环递归引用了 */
//new SyncLockDemo().add(); // 可重入锁也叫递归锁
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 外层");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 中层");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 内层");
}
}
}
}, "t1").start();
// Lock 演示
Lock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 外层");
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 内层");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "t1").start();
}
}
10. 死锁
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");
synchronized (b) {
System.out.println(Thread.currentThread().getName() + " 获取到锁b");
}
}
}, "AA").start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + ":: 持有锁b,试图获取锁a");
synchronized (a) {
System.out.println(Thread.currentThread().getName() + " 获取到锁a");
}
}
}, "BB").start();
}
}
10.1 什么是死锁?
- 两个或者两个以上进程在执行过程中,因为争夺资源而造成的一种互相等待的现象,如果没有外力干涉,他们无法在执行下去。
10.2 产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
10.3 验证是否是死锁
jps: 类似于 Linux 中的ps -ef,能查看当前运行中的进程jstack:能查看jvm中的堆栈跟踪工具
11. Callable 接口
11.1 Runnable 与 Callable
- 是否有返回值
- 是否抛出异常
- 实现方法名称不同,一个是
run方法,一个是call方法
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread1 implements Runnable {
@Override
public void run() {
}
}
class MyThread2 implements Callable {
@Override
public Integer call() throws Exception {
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread1(), "t1").start();
FutureTask<Integer> task = new FutureTask<Integer>(new MyThread2());
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 1024;
});
new Thread(futureTask2, "lucy").start();
while (!futureTask2.isDone()) {
System.out.println("wait......");
}
System.out.println(futureTask2.get());
System.out.println(futureTask2.get());
System.out.println(Thread.currentThread().getName() + " come over");
// FutureTask 原理,未来任务
/**
* 1、老师上课,口渴了,去买水不合适,讲课线程继续。
* 但开启线程,找班上班长帮我买水
* 把水买回来,需要的时候直接 get
* 2、4 个同学分别做计算, 1 同学 1+2+...+5 ;2 同学 10 + 11+12+...+50;
* 3 同学 60+61+62; 4 同学 100 + 200
*/
}
}
12. JUC 强大的辅助类
12.1 减少计数 CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDown() 方法来进行减 1 的操作,使用 await() 方法等待计数器不大于 0,然后继续执行 await() 方法之后的语句。
CountDownLatch主要有两个方法,当一个或者多个线程调用await()方法时,这些线程会阻塞;- 其它线程调用
countDown()方法会将计数器见 1(调用 countDown 方法的线程不会阻塞); - 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行。
public class CountDownLatchDemo {
// 六个同学陆续离开教室之后,班长锁门
public static void main(String[] args) throws InterruptedException {
CountDownLatch downLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
downLatch.countDown();
}, String.valueOf(i)).start();
}
downLatch.await();
System.out.println(Thread.currentThread().getName() + ":: 班长锁门走人了");
}
}
12.2 循环栅栏 CyclicBarrier
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
示例代码:
public class CyclicBarrierDemo {
// 创建固定值
private static final int NUMBER = 7;
// 集齐七颗龙珠召唤神龙
public static void main(String[] args) {
// 创建 CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("集齐七颗龙珠召唤神龙");
});
// 集齐七颗龙珠的过程
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 星龙被收集到了");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
12.3 信号灯 Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
示例代码:
public class SemaphoreDemo {
// 6 辆汽车,停 3 个车位
public static void main(String[] args) {
// 创建 Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
// 抢占
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 抢到了车位");
// 设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + " ------ 离开了车位");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}