文章目录
1、JUC简介
JUC是java.util.concurrent的缩写,即concurrent包下的所有东西,非常重要
在学习JUC前需要对Java多线程进行回忆并补充
2、线程和进程
进程和线程 详见操作系统
- 进程:就是一个正在运行的程序实例,进程是线程的容器
- 线程:一个程序中不同的功能可能由不同的线程并行执行,例如:打开QQ,你这边和A聊天,那边和B聊天
- 对java而言:java默认有2个线程:
main线程
和gc线程
- 基类:
Thread
,Runnable
,Callable
- 对java而言:java默认有2个线程:
对于Java而言,真的能开启线程吗? 不行
Thread的start()方法底层调用的是native关键字的本地方法start0(),底层是c++,java无法直接操作硬件
并发与并行
- 并发concurrency: 即多线程操作同一个资源
- CPU 一核,模拟多条线程,交替访问资源
- 并行parallellism: 多个人一块行走(线程池)
- CPU 多核,多条线程在不同核心上同一时刻执行
**并发编程的本质:**为了充分利用CPU的资源
线程的状态
操作系统中的五大状态
- 创建状态: 进程由创建产生,首先申请一个空白的PCB控制块,像PCB块中填写相应信息,然后分配所需的资源,最后转为就绪状态插入就绪队列中。
- 就绪状态: 已经准备好可以运行的状态,只要获得cpu就可以立即执行,进入运行状态。
- 运行状态: 已经获得cpu的进程,处于正在执行的状态
- 阻塞状态: 在执行过程中,缺少资源(如I/O请求),无法继续执行,即进入阻塞态,cpu调度将该阻塞的进程移到阻塞队列,再从就绪队列中调一个运行
- 终止状态: 进程的任务完成,到达自然结束点,操作系统将其PCB清空,并返还给系统
Java中源码分为六个状态
查看源码枚举类:State
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
- 新生(NEW): 新创建了一个线程但是没调用start()方法,即尚未启动的线程
- 运行态(RUNNABLE): Java将线程中**就绪(ready)的运行(running)**中的两种状态笼统地称为运行
- 阻塞态(BLOCKED): 一个线程因为等待临界区的锁被阻塞产生的状态
- 等待(WAITING): 正在等待另一个线程做出一些特定动作:如通知或者中断
- 超时等待(TIMED_WAITING): 在waiting基础上加了超时时间,可以在指定时间到达后自行返回
- 终止(TERMINATED): 表示线程执行结束,退出
wait和sleep的区别
1、来自不同的类
wait:wait是Object类下的
sleep:sleep是线程Thread类独有的
2、锁的释放
wait:会释放锁
sleep:不会释放锁,抱着锁睡觉
3、使用范围不同
wait:只能在同步代码块或同步方法中使用
sleep:在任何地方都能使用
4、是否需要捕获异常
wait:不需要
sleep:必须捕获异常InterpretedException
3、线程锁
3.1 synchronized
真正的开发,线程就是一个单独的资源类,用方法进行资源的使用
public class SynchronizedDemo {
public static void main(String[] args) {
//多个线程操作一个资源类:Ticket
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"C").start();
}
}
// 资源类 OOP
class Ticket {
// 属性、方法
private int number = 30;
// 卖票的方式
// synchronized 本质: 队列,锁
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+(number--)+"张票,剩余:"+number);
}
}
}
3.2 Lock
Lock使用的三步:
- new ReentrantLock(); new一个锁
- lock.lock(); 加锁
- finally => lock.unlock(); 在finally里解锁
public class LockDemo {
static class Ticket{
private int number = 30;
private Lock lock = new ReentrantLock();
public void sale(){
//进入方法时加锁
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+(number--)+"张票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//在结束时释放锁
lock.unlock();
}
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
ticket.sale();
}
},"C").start();
}
}
注:
ReentrantLock默认(无参构造)是非公平锁
可以通过有参构造改为公平锁
- 公平锁:先来后到,不允许插队
- 非公平锁:可以插队
3.3 Synchronized和Lock区别
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁
- Synchronized 线程1(获得锁,阻塞)线程2(等待,傻傻的等;Lock锁就不一定会等待下去;
- Synchronized 可重入锁,不可以中断的,非公平锁;Lock ,可重入锁,可以 判断锁,非公平(可以 自己设置);
- Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码
4、线程通信
线程通信经典问题:生产者消费者问题
4.1 Synchronized版
if判断的问题
public class SynchronizedDemo {
//资源类
static class Data{
private int num = 0;
//加一操作
public synchronized void