文章目录
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 increment() throws InterruptedException {
if (num!=0){//需要改为while,后续会讲
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//+1操作执行完成提醒其他线程,自己加一操作完毕了
this.notifyAll();
}
//减一操作
public synchronized void decrement() throws InterruptedException {
if (num==0){//需要改为while,后续会讲
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//+1操作执行完成提醒其他线程,自己加一操作完毕了
this.notifyAll();
}
}
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 1;i<=10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
出现问题:虚假唤醒
原因:查看jdk文档
if判断改为while判断
//加一操作
public synchronized void increment() throws InterruptedException {
while(num!=0){//需要改为while,后续会讲
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//+1操作执行完成提醒其他线程,自己加一操作完毕了
this.notifyAll();
}
//减一操作
public synchronized void decrement() throws InterruptedException {
while(num==0){//需要改为while,后续会讲
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//+1操作执行完成提醒其他线程,自己加一操作完毕了
this.notifyAll();
}
}
4.2 JUC版线程通信
- Synchronized使用
wait
和notify
- Lock使用Condition类下的
await
和signal
怎么使用:
用Lock类下的newCondition()
方法
基本使用
public class LockDemo {
static class Data{
private int num = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//加一操作
public void increment(){
lock.lock();
try {
while (num!=0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//+1操作执行完成提醒其他线程,自己加一操作完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
//减一操作
public void decrement(){
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//-1操作执行完成提醒其他线程,自己减一操作完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 1;i<=10;i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
data.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
data.increment();
}
},"C").start();
new Thread(()->{
for (int i = 1;i<=10;i++) {
data.decrement();
}
},"D").start();
}
}
同样的:
判断需要用while而不是if
Condition精准通知和唤醒
实现:A唤醒B,B唤醒C,C唤醒A
package com.bandit.JUCConsumerAndProducer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Bandit
* @create 2022/1/6 12:16
*/
public class LockDemo02 {
static class Data{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
// 唤醒,唤醒指定的人,B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=2){
// 等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBB");
// 唤醒,唤醒指定的人,B
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
// 业务,判断-> 执行-> 通知
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCC");
// 唤醒,唤醒指定的人,A
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
5、8锁问题
深刻理解锁
例一、二:
1、标准情况下,两个线程先打印 发短信还是 打电话? 发短信
2、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 发短信
/**
* 1、标准情况下,两个线程先打印 发短信还是 打电话? 发短信
* 2、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 发短信
*/
public class Test1 {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
/**
*例一 的Phone
*/
static class Phone{
public synchronized void sendSms(){
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
}
/**
*例二 的Phone
*/
static class Phone{
public synchronized void sendSms(){
try {//在发短信前sleep,看看谁先执行
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
}
}
解释: synchronized 锁的对象是方法的调用者。 两个方法用的是同一个锁,谁先拿到谁执行!
此处A先拿到锁,无论A睡多少秒都是A先执行
例三:
3、新增一个普通方法,先执行hello还是发短信? hello
/**
* 3、新增一个普通方法,先执行hello还是发短信? hello
*/
public class Test1 {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"A").start();
}
/**
*例三 的Phone
*/
static class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
public void hello(){
System.out.println("hello");
}
}
}
hello是非同步方法,异步执行,发短信要等4秒,hello只要等1秒,所有hello先执行
例四:
4、 两个对象,两个同步方法, 发短信还是 打电话? 打电话
/**
* 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone phone1 = new Phone();
Phone phone2 = new Phone();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
static class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
}
解释: synchronized锁的是方法的调用者,两个对象的锁不一样,所以互不影响,先执行打电话再发短信
例五、六:
5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话? 发短信
6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话? 发短信
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话? 发短信
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话? 发短信
*/
public class Test3 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
new Thread(()->{
phone1.call();
},"B").start();
*/
new Thread(()->{
phone2.call();
},"B").start();
}
static class Phone{
//加上static修饰,类一加载就锁上了,
//static锁的是 Class 模板,类锁
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
}
解释: 加上static的synchronized 是类锁,类一加载就有了,无论几个对象都是用的一把锁,
所以是先发短信再打电话
例七、八:
7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话? 打电话
8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话? 打电话
/**
* 7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
* 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
*/
public class Test4 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
new Thread(()->{
phone1.call();
},"B").start();
*/
new Thread(()->{
phone2.call();
},"B").start();
}
static class Phone{
//静态同步方法 锁的是Class类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法 锁的是方法的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
}
解释: 静态同步方法锁的是Class类模板,普通同步方法锁的是方法的调用者
两个方法对应的是两把锁,不影响,所以先打电话再发短信