Juc并发编程
1、什么是JUC
JUC:Java原生的并发包,和一些常用的工具类!
2、线程基础知识回顾
什么是进程和线程?
进程:idea.exe
线程:打字、自动保存…
一个进程可以包含多个线程,一个进程至少有一个线程! Java程序至少有两个线程: GC、Main
并发、并行
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
所以我们使用多线程或者并发编程的目的:提高效率,让CPU一直工作,达到最高处理性能!
线程有几种状态
线程有 6 种状态,面向源码学习!
public enum State {
// java能够创建线程吗? 不能!只能通过native调用本地方法实现
// 新建
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待
WAITING,
// 延时等待
TIMED_WAITING,
// 终止!
TERMINATED;
}
wait/Sleep区别
1、类不同!
wait : Obejct 类 Sleep Thread
在juc编程中,线程休眠怎么实现!Thread.Sleep
// 时间单位
TimeUnit.SECONDS.sleep(3);
2、会不会释放资源!
-
sleep:抱着锁睡得,不会释放锁!
-
wait :会释放锁!
3、使用的范围是不同的;
wait 和 notify 是一组,一般在线程通信的时候使用!
sleep 就是一个单独的方法,在哪里都可以用!
4、关于异常;
sleep 需要捕获异常!
3、Lock锁
synchronized 传统的方式!
代码:
package com.coding.demo01;
// 传统的 Synchronized
// Synchronized 方法 和 Synchronized 块
/*
* 1、架构:高内聚,低耦合
* 2、套路:线程操作资源类,资源类是单独的
*/
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
// 1、新建资源类
Ticket ticket = new Ticket();
// 2、线程操纵资源类
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
// 单独的资源类,属性和方法!
// 这样才能实现复用!
class Ticket{
private int number = 30;
// 同步锁,厕所 =>close=>
// synchronized 这是一个关键字
public synchronized void saleTicket(){
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number);
}
}
}
问题:
Lock 锁
代码:
package com.coding.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* JUC之后的操作
* Lock锁 + lambda表达式!
*/
public class Demo02 {
public static void main(String[] args) {
// 1、新建资源类
Ticket2 ticket = new Ticket2();
// 2、线程操作资源类 , 所有的函数式接口都可以用 lambda表达式简化!
// lambda表达式 (参数)->{具体的代码}
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"A").start();
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"B").start();
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"C").start();
}
}
// 依旧是一个资源类
class Ticket2{
// 使用Lock,它是一个对象
// ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...)
// ReentrantLock 默认是非公平锁!
// 非公平锁: 不公平 (插队,后面的线程可以插队)
// 公平锁: 公平(只能排队,后面的线程无法插队)
private Lock lock = new ReentrantLock();
private int number = 30;
public void saleTicket(){
lock.lock(); // 加锁
try {
// 业务代码
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}
Synchronized 和 Lock 区别
1、Synchronized 是一个关键字、Lock 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;
4、生产者消费者问题
面试手写题:单例模式、排序算法、死锁、生产者消费者
线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:
Synchronized 普通版
package com.coding.demo01;
// Synchronized 版
/*
目的: 有两个线程:A B ,还有一个值初始为0,
实现两个线程交替执行,对该变量 + 1,-1;交替10次
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
// +1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// -1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 资源类
// 线程之间的通信: 判断 执行 通知
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()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number==0){ // 判断是否需要等待
this.wait();
}
number--; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
}
四条线程可以实现交替吗?不能,会产生虚假唤醒问题!
注意if 和 while的区别:
package com.coding.demo01;
// Synchronized 版
/*
目的: 有两个线程:A B ,还有一个值初始为0,
实现两个线程交替执行,对该变量 + 1,-1;交替10次
传统的 wait 和 notify方法不能实现精准唤醒通知!
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
// +1
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.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
// -1
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.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
// 资源类
// 线程之间的通信: 判断 执行 通知
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()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
// -1
public synchronized void decrement() throws InterruptedException {
while (number==0){ // 判断是否需要等待
this.wait();
}
number--; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
}
新版的写法 JUC 挂钩!
package JUC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
实现线程交替执行!
主要的实现目标:精准的唤醒线程!
三个线程:A B C
三个方法:A p5 B p10 C p15 依次循环
*/
public class ConditionLearn {
public static void main(String[] args){
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread01").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread02").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread03").start();
}
}
class Data{
private int number = 1;
private Lock lock = new ReentrantLock();
//实现精准访问
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5() throws InterruptedException {
lock.lock();
try {
//判断
while (number != 1){
condition1.await();
}
//执行
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 2;
condition2.signal();//唤醒
} finally {
lock.unlock();
}
}
public void print10() throws InterruptedException {
lock.lock();
try {
//判断
while (number != 2){
condition2.await();
}
//执行
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 3;
condition3.signal();//唤醒
} finally {
lock.unlock();
}
}
public void print15() throws InterruptedException {
lock.lock();
try {
//判断
while (number != 3){
condition3.await();
}
//执行
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//通知
number = 1;
condition1.signal();//唤醒
} finally {
lock.unlock();
}
}
}