发现 学习狂神JUC系列少了开篇 特此补发
线程、进程、如何来解释
进程 : 一个程序 如 QQ.exe Music.exe 程序的集合
一个进程可以包含多个线程,至少包含一个线程
Java 默认是开启两个线程 main GC
线程: 开了一个进程 比如: typora 写字,自动保存(线程负责的)
对于Java 而言创建线程我们学习到的方法有三种 : Thread , Runnable , Callable
PS :Java本身是无法开启线程的!!!
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 本地方法 调用底层的 C++ Java本身不能直接操作硬件
private native void start0();
并发,并行
我们要学习并发编程,首先要了解 : 并发和并行是什么
并发(多线程操作同一资源)
并行(多个程序一起走)
查看一下自己的cpu核数
并发编程的本质:充分利用CPU的资源
所有的公司都很看重,就是效率,比如裁员:一个厉害的可以大于两个不再那么厉害的甚至更多
比如: 做事情的速度 高手:1s,一般人: 10s 肯定是 1s 的更加的之前也更加的有效率;
做到人员(减少),但是技术力却提高了
线程有几个状态
通过Thread 源码里的枚举类:State 中的属性可以看出来
线程有六个状态
public e
num State {
//新的线程
NEW,
//运行中
RUNNABLE,
// 阻塞
BLOCKED,
//等待,一直等
WAITING,
//等待一段时间就不等了
TIMED_WAITING,
// 终止线程
TERMINATED;
}
wait/sleep的区别
1、他们来自不同的类
wait => object
sleep => Thread
2、关于锁的释放
wait会释放锁,sleep不会释放锁
抽象理解 : sleep 睡着了没办法释放, wait 是等待,有人需要的释放
wait必须在同步代码块中使用,得有需要等的人
sleep可以在任何地方使用
3、是否需要捕获异常
wait 不需要捕获异常
sleep 需要捕获异常
传统 Synchronized
传统火车票案例
/**
* @projectName: JUC
* @package: Thread
* @className: SaleTicketDemo
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 20:28
* @version: 1.0
*/
public class SaleTicketDemo {
/**
* 真正的多线程开发 公司中的需要降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1、 属性。方法
* */
public static void main(String[] args) {
//并发: 多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "C").start();
}
}
//资源类 OOP
class Ticket {
//属性 方法
private int number = 50;
// 买票的方式
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
}
}
}
Lock接口
常用的一些实现类
公平锁 : 十分公平,可以先来后到 比如 前一个线程要执行30s 后面的 需要3s 后者必须等待前者执行完
非公平锁 : 十分不公平,可以根据条件来插队
如何用Lock来编写火车票Demo
Synchronized和Lock区别
锁是什么,如何判断锁的是谁
面试高频 : 单例模式,排序算法,生产者和消费者 死锁
package ProduceAndconsum;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: A
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:08
* @version: 1.0
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data {
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (number != 0) {
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 +1 完毕了
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 -1 完毕了
this.notifyAll();
}
}
但是,写出来这个简单的模型 面试官还是会挑的出毛病来,问题出在那?
问题,现在我们是两个线程 ,我们加到四个线程 A,B,C,D 现在还是安全的吗?答案是肯定的不是
虚假唤醒问题:我们增加到四个线程输出的时候就会发现一些问题来,输出不再是0101了
这里如何解决呢? 将 if 换成 while循环
修改之后根据官方文档的解释之后,将if改编成while
输出就又回到了正常
package ProduceAndconsum;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: A
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:08
* @version: 1.0
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data {
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
while (number != 0) {
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 +1 完毕了
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 -1 完毕了
this.notifyAll();
}
}
在新的学习中 synchronized 有 Lock 替换
那么 wait 和 notify谁来替换呢?
通过 Lock来找到 Condition
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//等待
// condition.await();
//唤醒全部
// condition.signalAll();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while (number != 0) {
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 +1 完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 -1 完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//等待
// condition.await();
//唤醒全部
// condition.signalAll();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while (number != 0) {
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 +1 完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程 -1 完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何的一个新技术,都不可能是单纯的去替代老的技术,一定有优化和补充
Condition
我们更换完代码之后,可以正常输出但是还是混乱的,我们想要达到 A->B->C->D 这样输出,这个时候就引出了新的知识点
Conddition精准唤醒
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
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();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data3 {
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void printA() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 1) {
//等待
condition.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAA");
number = 2;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 2) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
number = 3;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 3) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
number = 1;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出的结果就达到我们的预期了
锁 ----> 刚new 出来的对象、class
8锁就是关于的锁的八个问题,下面也有四个demo类来阐述各种场景下锁的不同状态
demo1
两个问题:
正常模式下 在两个同步线程方法调用的时候 中途延时1s 会不会改变输出结果
答:不会影响输出顺序
正常模式下 同步方法内延时4s 会不会印象输出
答:不会影响输出顺序
demo代码
package lock8;
import java.util.concurrent.TimeUnit;
/**
* @projectName: JUC
* @package: Lock8
* @className: lock8Demo
* @author: 冷环渊 doomwatcher
* @description: TODO
* Lock8 就是关于锁的八个问题
* 1. 标准情况下 两个线程打印 发短信还是打电话 1/ 发短信 2/打电话
* 2. 发短信延时四秒 两个线程打印 发短信还是打电话 1/ 发短信 2/打电话
* @date: 2022/3/2 1:13
* @version: 1.0
*/
public class lock8Demo {
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();
}
}
class phone {
//synchronized 锁的对象是方法调用者,
//两个方法用的都是 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("打电话");
}
}
demo2
同步方法执行和普通方法执行顺序
答 :普通方法没有锁,所以普通方法先
两个对象执行会不会影响顺序
答:会 两个不同的对象锁也是不同的 ,对象1 还在等待,对象2调用的call方法不用等待所以先输出
package lock8;
import java.util.concurrent.TimeUnit;
/**
* @projectName: JUC
* @package: Lock8
* @className: lock8Demo
* @author: 冷环渊 doomwatcher
* @description: TODO
* Lock8 就是关于锁的八个问题
* 3、 现在新增一个普通方法 问 先发短信还是先发hello 先输出hello
* 4、 新增两个对象 是先打电话还是先发短信
* @date: 2022/3/2 1:13
* @version: 1.0
*/
public class lock8Demo2 {
public static void main(String[] args) {
//两个对象,现在是两个调用者所以是两个锁
phone2 phone = new phone2();
phone2 phone1 = new phone2();
new Thread(() -> {
phone.sendSms();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.call();
}, "B").start();
}
}
class phone2 {
//synchronized 锁的对象是方法调用者,
//两个方法用的都是 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");
}
}
demo3
一个对象调用静态同步方法会不会改动执行顺序
答: 不会 ,static是锁的类模版全局唯一,不会改变锁的执行交换顺序
两个对象调用静态同步方法会不会改变执行顺序
答:不会 static修饰的是类模版,锁的也是类模板而不是类对象,只要是这个类生成的对象,不管多少个都不会改变顺序
package lock8;
import java.util.concurrent.TimeUnit;
/**
* @projectName: JUC
* @package: Lock8
* @className: lock8Demo
* @author: 冷环渊 doomwatcher
* @description: TODO
* Lock8 就是关于锁的八个问题
* 5、添加两个静态的同步方法,只有一个对象 先打印 发短信 还是打电话?
* 6、 添加两个对象,增加两个同步方法 先打印 发短信 还是打电话?
* @date: 2022/3/2 1:13
* @version: 1.0
*/
public class lock8Demo3 {
public static void main(String[] args) {
//不管多少对象, 使用的都是底层的唯一class所以不管怎么怎么改变结果都不会变,
// 因为synchroized锁的是class对象,static修饰的方法 类一开始就加载了,
new Thread(() -> {
Phone3.sendSms();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
Phone3.call();
}, "B").start();
}
}
//phone3是唯一的一个class
class Phone3 {
//synchronized 锁的对象是方法调用者,
//两个方法用的都是 phone 对象的锁,两个方法谁先拿到锁 谁执行
// 这里 static 是静态方法 ,类一加载就有了,这个用的锁不再是 phone锁 而是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("打电话");
}
}
demo4
答: 不是同一个锁,谁执行快就输出谁,
package lock8;
import java.util.concurrent.TimeUnit;
/**
* @projectName: JUC
* @package: Lock8
* @className: lock8Demo
* @author: 冷环渊 doomwatcher
* @description: TODO
* Lock8 就是关于锁的八个问题
* 7、一个静态方法和一个普通加锁方法, 先打印 发短信 还是打电话? 先出打电话,因为锁的不是一个东西,sync锁的是类,static 是锁class
* 8、两个对象 一个静态方法和一个普通加锁方法, 先打印 发短信 还是打电话? 先出打电话,
* @date: 2022/3/2 1:13
* @version: 1.0
*/
public class lock8Demo4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
//不管多少对象, 使用的都是底层的唯一class所以不管怎么怎么改变结果都不会变,
// 因为synchroized锁的是class对象,static修饰的方法 类一开始就加载了,
new Thread(() -> {
Phone4.sendSms();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//phone3是唯一的一个class
class Phone4 {
//静态同步方法是锁的 class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//这里普通同步方法锁的是 phone
public synchronized void call() {
System.out.println("打电话");
}
}
小结
我们通过八种不同的情况来理解锁在不同情况下的执行情况:
大家可以自己写一下代码看一些这些问题的结果,实践出效果,实践出理解
锁的东西,无非就是对象和 类模版