JUC介绍
JUC是jdk中java.util.concurrent包的简称。改包提供了并发编程中常用的工具类。
线程和进程
进程:运行中的程序,一个进程包含多个线程。
线程:进程中的单一串行程序片段
线程创建方式
线程创建有四种方式:继承Thread类、实现Runnable接口结合Thread类、利用Callable接口,FutureTast类结合Thread(有返回值时用这个)、线程池创建
1、继承Thread类
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 19:51 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class One extends Thread {
@Override
public void run() {
for (int i =0;i<=1000;i++){
log.debug("----->{}",i);
}
}
public static void main(String[] args) {
Thread t = new One();
t.start();
}
}
2、结合Thread类实现Runnable接口
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 20:04 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Two implements Runnable {
@Override
public void run() {
for (int i =0;i<=1000;i++){
log.debug("----->{}",i);
}
}
public static void main(String[] args) {
Two two = new Two();
Thread t = new Thread(two);
t.start();
}
}
进阶1 内部类方法实现
Thread t1 = new Thread(){
@Override
public void run() {
for (int i =0;i<=1000;i++){
log.debug("----->()",i);
}
}
};
t1.start();
进阶2 lamada表达式实现
Thread t1 = new Thread(()->{
for(int i=1;i<=1000;i++){
log.debug("{}----->{}", Thread.currentThread().getName(),i);
}
},"T1");
3、FutrueTask类结合Thread实现Callable接口
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* @Author: curry
* @Date: Created in 20:15 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Three {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return 100;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t1 = new Thread(futureTask);
t1.start();
//get方法是阻塞的。一直到内部的call方法执行完毕才返回
Integer k = futureTask.get();
log.debug("{}",k);
System.out.println(k);
}
}
4、线程池创建
线程的状态
新建:刚刚new出来的线程对象
就绪:已调用start,但尚未运行,但正在积极竞争cpu
运行:正在运行
阻塞:不竞争cpu(即使cpu空闲)阻塞状态结束进入就绪状态
死亡:线程运行结束
yield、wait,join,sleep的区别
阻塞:wait、sleep、join
非阻塞:yield
Thread.yield()使当前线程进入就绪状态(也叫做让步方法,由运行状态编程就绪状态,让出cpu的使用权) (我自己的理解,当有多个线程时,线程一执行yield方法后,让出cpu的使用权,下一刻还有可能是线程一抢占到cpu的使用权)
Thread.sleep()使当前线程进入阻塞状态一段时间,调用该方法后,cpu极大可能被另一个线程抢占
join()方法(线程实例方法)使当前线程进入阻塞状态,等待被调用join方法的线程执行完毕后,再接着执行。如果在当前线程中调用另一个线程的join方法,则会等待另一个线程执行完毕(即使另一个线程就绪或阻塞),才会执行
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 20:23 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Four {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for(int i=1;i<=1000;i++){
log.debug("{}----->{}", Thread.currentThread().getName(),i);
}
},"T1");
Thread t2 = new Thread(()->{
for (int i =1;i<=1000;i++){
log.debug("{}------>{}",Thread.currentThread().getName(),i);
if(i==200){
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"T2");
t1.start();
t2.start();
}
}
wait()方法(Object实例方法)使当前线程阻塞,需要其他线程调用notify或者notifyAll方法
线程优先级
多个运行状态的线程,其中优先级较高的竞争上cpu的概率较高
优先级:1~10(越大优先级越高)优先级较高的线程被cpu调用的概率高
通过setPriority设置线程的优先级
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 20:23 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Four {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for(int i=1;i<=1000;i++){
log.debug("{}----->{}", Thread.currentThread().getName(),i);
}
},"T1");
Thread t2 = new Thread(()->{
for (int i =1;i<=1000;i++){
log.debug("{}------>{}",Thread.currentThread().getName(),i);
// if(i==200){
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
},"T2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
线程中断interrupt(中断)
1.对于不在阻塞状态的线程,interrupt方法仅仅修改中断状态为true,不影响线程的执行
2.对于阻塞的线程,interrupt方法将终止阻塞并抛出interruptedException,同时恢复中断状态为false
对于中断线程怎么处理由开发者决定,根据isinterred来判断是否处于中断状态
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 20:38 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Five {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i=1;i<=1000;i++) {
log.debug("--->()", i, Thread.currentThread().getName());
if (Thread.currentThread().isInterrupted()) {
//假如线程处于阻塞状态,则return
return;
}
Thread.yield();
}
},"T2");
t1.start();
Thread.sleep(10);
t1.interrupt(); //向被调用的线程发出中断信号
//线程可以收到中断信号,但是收到的中断信号的逻辑由开发者决定
}
}
守护线程
垃圾回收线程是一个守护线程
守护线程是为其他线程服务的线程,随着其他线程的结束而结束
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 20:54 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Six {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i=0;i<=1000;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是守护线程。。。"+i);
}
});
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
}
}
如果不设置t1为守护线程则这个线程会一直运行,不会终止
互斥
synchronize是同步代码块的简洁写法
同步实例方法:以this对象为锁,以整个方法体为同步代码块
同步静态方法:以所属类的元对象为锁,以整个方法为同步代码块
线程安全问题
1.基本类型的局部变量和变量所引用对象的作用范围不超出局部范围的引用类型变量没有线程安全问题
2.属性(类的实例属性和静态属性)需要考虑线程安全问题
3.变量所引用对象作用范围超出局部范围的引用类型变量需要考虑线程安全问题
线程同步-协作(生产者和消费者问题)
场景:
一个面包柜只能容纳5个面包,面包师傅每天烤100个面包放入面包柜,店员每天从面包柜中取100个面包销售
操作:
师傅每放入一个面包就通知(notify)店员可以取了,如果师傅发现柜满了,则等待(wait)指导被唤醒(通知)
店员每取出一个面包就通知师傅可以放了,如果店员发现柜子空了,则等待直到被唤醒(通知)
满了就让师傅等待,空了就让店员等待
java.lang.Object提供了三个方法
wait()只能在同步代码块中调用,而且只能通过锁对象调用,使当前线程基于所在代码锁等待
notify()只能在同步代码块中调用,而且只能通过锁对象调用。唤醒【基于当前线程锁在代码锁等待的】线程中的一个
notifyAll() 只能在同步代码块中调用,而且只能通过锁对象调用,唤醒【基于当前线程所在代码锁等待的】所有线程
实现代码
柜子类
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 21:10 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class Guizi {
private int cap; //容量
private int size; //数量
public Guizi(int cap) {
this.cap = cap;
this.size = 0;
}
public boolean isFull(){
return size==cap;
}
public boolean isEmpty(){
return size==0;
}
public synchronized void add(){
while (this.isFull()){
try {
log.debug("柜子慢,等待中。。。");
this.notifyAll();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有空位,被唤醒。。。");
size++;
log.debug("放入一个面包,面包数为:{}",size);
}
public synchronized void remove(){
while (this.isEmpty()){
try {
log.debug("柜子空,等待中。。。");
this.notifyAll();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有面包,被唤醒。。。");
size--;
log.debug("取出一个面包,面包数为:{}",size);
}
}
启动类
package com.hzz.test;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: curry
* @Date: Created in 21:19 2022/11/30
* @Modified By:
* @description:
*/
@Slf4j
public class GuiziTest {
public static void main(String[] args) {
Guizi guizi = new Guizi(5);
for(int k=0;k<7;k++){
new Thread(()->{
for (int i=0;i<100;i++){
guizi.add();
Thread.yield();
}
},"面包师"+k).start();
new Thread(()->{
for (int i=0;i<100;i++){
guizi.remove();
Thread.yield();
}
},"店员"+k).start();
}
}
}
synchronize锁升级
偏向锁->轻量级锁->重量级锁
偏向锁:仅有一个线程访问,当有另一个线程访问时升级为轻量级锁
轻量级锁:有多个线程不同时间访问,当多个线程同时访问时产生锁自选
锁的自选:当一个线程持有锁,另一个线程访问同步代码块是并不阻塞,而是进入循环,等待取到同步代码块的锁后执行,循环次数大于jvm设置的上限之后升级为重量级锁。(处于运行状态,还在占用cpu,减少在运行态和阻塞态之间切换的消耗)
重量级锁:当锁自旋次数大于jvm设置的上限之后,还没有获得锁的线程进入阻塞状态
JAVA内存模型(JVM)
多线程把内存分为主内存和工作内存,工作内存是线程独有的
当一个线程从非运行状态变成运行状态是会从主存中更新工作内存的数据
当一个线程拿到锁之后会从主内存拿取数据,释放锁的时候将数据向主内存回传
线程只要遇到锁,就会从主内存中更新工作内存
注:被volatile修饰的变量,主内存的值改变后,立即通知各工作内存从主内存中重新读取值
JVM三大特性
原子性:如何保证指令不会收到线程上下文切换的影响。线程上下文切换主要指运行态和阻塞态之间的转换,这个切换消耗成本较大
可见性:如何保证指令不受cpu缓存的影响(工作内存和主内存的值不一样)
有序性:如何保证指令不受cpu指令优化的影响(cpu进行优化时可能进行指令重排)
synchronize保证原子性、可见性
volatile可以保证可见性、有序性,但不保证原子性(适合一个线程写,多个线程读的情况)
CAS无锁技术
CAS即比较并设置,是一种无锁的、非阻塞的线程并发安全技术
CAS操作:设置新值时,首先将数据的当前值和预期原值进行比较,如果一致则更新为新值,否者不更新
CAS和volatile结合实现了安全的原子性并发,在java中提供的原子数据类型就是这样做的
公平锁和非公平锁
公平锁:按照请求锁的顺序依次获得锁,好处是每个线程都有获得锁的机会,效率低一些
一开始时是线程一处于第一位,线程一执行完后释放锁,再重新排队
package com.hzz.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: curry
* @Date: Created in 22:39 2022/11/30
* @Modified By:
* @description:
*/
public class Task implements Runnable {
Lock l = new ReentrantLock(true); //ReentrantLock构造方法中的参数为true时是公平锁,为false时是非公平锁
@Override
public void run() {
while (true){
l.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
package com.hzz.test;
/**
* @Author: curry
* @Date: Created in 22:11 2022/11/30
* @Modified By:
* @description: 公平锁和非公平锁
*/
public class TestLock {
public static void main(String[] args) {
//公平锁测试
Task task = new Task();
Thread t1 = new Thread(task,"线程一");
Thread t2 = new Thread(task,"线程二");
Thread t3 = new Thread(task,"线程三");
t1.start();
t2.start();
t3.start();
}
}
非公平锁:不按照请求顺序获得锁,而是随机顺序获得锁,好处是效率高,但是有的线程没有获得锁的机会(锁饥饿)
synchronize是非公平锁
package com.hzz.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: curry
* @Date: Created in 22:39 2022/11/30
* @Modified By:
* @description:
*/
public class Task implements Runnable {
Lock l = new ReentrantLock(true); //ReentrantLock构造方法中的参数为true时是公平锁,为false时是非公平锁
@Override
public void run() {
while (true){
l.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
package com.hzz.test;
/**
* @Author: curry
* @Date: Created in 22:11 2022/11/30
* @Modified By:
* @description: 公平锁和非公平锁
*/
public class TestLock {
public static void main(String[] args) {
//公平锁测试
Task task = new Task();
Thread t1 = new Thread(task,"线程一");
Thread t2 = new Thread(task,"线程二");
Thread t3 = new Thread(task,"线程三");
t1.start();
t2.start();
t3.start();
}
}
Lock接口
Lock接口有三个实现类,分别是可重入锁ReentranLock、读锁ReadLock、写锁writeLock
可重入锁(ReentranLock)
本线程已经有这个锁之后再获得一次这个锁