JUC
介绍
JUC是jdk中java.util.concurrent包的简称,该包提供了并发编程中常用的工具类。概括地说,JUC的就 是java并发编程工具包。
目前juc泛指Java多线程开发技术
线程和进程
进程:运行中的程序,一个进程包括很多线程
线程:进程中的(运行中的)单一串行程序片段
线程的创建方式
-
继承Thread
package org.juc.线程创建的几种方式;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class One extends Thread{
@Override
public void run() {
for(int i=1;i<=1000;i++){
log.debug("----->{}",i);
}
}
public static void main(String[] args) {
Thread t = new One();
t.start();
}
}
2.使用Runnable接口结合Thread类
package org.juc.线程创建的几种方式;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Two implements Runnable{
@Override
public void run() {
for(int i=1;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 = 1;i <= 1000;i++){
log.debug("----->()",this.getName(),i);
}
}
};
第二种方式 进阶2:lamada表达式
Thread t2 = new Thread(()->{
for (int i = 1;i <= 1000;i++){
log.debug("----->()",Thread.currentThread().getName(),i);
}
},"T2");
3.利用Callable接口、FutureTast类结合Thread(有返回值时用这个)(不常用)
package org.juc.线程创建的几种方式;
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;
@Slf4j
public class Three {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable() {
@Override
public Object 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空闲),阻塞状态结束进入就绪状态
-
死亡:线程运行结束
阻塞状态不竞争cpu,阻塞状态结束后才竞争cpu,就绪状态正在竞争cpu 阻塞已经启动过了,就绪还未启动,阻塞结束后进行就绪
与线程相关的API
Thread.yield() 致使当前线程进入就绪状态(也叫做让步方法,由运行状态变成就绪状态,让出cpu的使用权)
Thread.sleep() 致使当前线程进入阻塞状态一段时间
join方法(线程实例方法)致使当前线程进入阻塞状态,等待被调用join方法的线程执行完毕后,再接着执行。如果在当前线程中调用另外一个线程的join方法,则就会等待另外一个线程(即使另外一个线程就绪或阻塞)执行完毕,才会执行。
package org.juc;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test02 {
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();//等待t1执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"T2");
t1.start();
t2.start();
}
}
wait方法(Object实例的方法)致使当前线程阻塞,需要其它线程调用notify或者notifyAll方法。
sleep是睡眠一段时间 wait是进入阻塞,没有notify方法不会继续执行
线程的优先级
多个运行态的线程,其中优先级较高的,竞争上cpu的概率较高。
优先级:1~10(越大优先级越高)
-
优先级较高的线程被cpu调用的概率更高
通过线程的setPriority设置线程的优先级
线程中断interrupt(中断)
1.对于不在阻塞状态的线程,interrupt方法仅仅修改中断状态为true,不影响线程的执行
2.对于阻塞的线程,interrupt方法将终止阻塞并抛出InterruptedException,同时恢复中断状态为 false(isInterrupted())。
对中断线程怎么处理由开发者决定,根据isinterrupted来进判断是否处于中断状态
package org.juc.线程中断;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 1;i <= 1000;i++){
log.debug("----->()",i,Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()){
//假如线程处于阻塞状态,则return
return;
}
Thread.yield();
}
},"T2");
t1.start();
Thread.sleep(10);
t1.interrupt(); //向被调用的线程发送中断信号
// 线程可以收到中断信号,但是收到中断信号的逻辑由开发者决定
}
}
守护线程
-
垃圾回收线程是一个守护线程
守护线程是为其他线程服务的线程,随着其他线程的结束而结束
package org.juc;
public class Test04 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是守护线程。。。");
}
});
t.setDaemon(true);
t.start();
Thread.sleep(1000);
}
}
输出结果:守护线程运行两次后结束
如果不设置t为守护线程,则这个线程会一直运行,不会终止
互斥
synchronized是同步代码块的简洁写法 同步实例方法:以this对象为锁,以整个方法体为同步代码块 同步静态方法:以所属类的元对象为锁,以整个方法体为同步代码块
变量的线程安全问题
-
基本类型的局部变量和变量所引用对象作用范围不超出局部范围的引用类型变量没有线程安全问题。
-
属性(类的实例属性和静态属性)需要考虑线程安全问题
-
变量所引用对象作用范围超出局部范围的引用类型变量需要考虑线程安全问题
StringBuffer是线程安全的,StringBuilder是线程不安全的,StringBuilder效率比StringBuffer效率高,加锁导致效率降低
线程的同步-协作(生产者和消费者问题)
场景:
一个面包柜只能容纳5个面包,面包师傅每天拷100个面包放入面包柜,店员每天从面包柜中取 100个面包销售。
操作:
师傅每放入一个面包就通知(notify)店员可以取了,如果师傅发现柜子满了,则等待(wait)直到被唤醒(通知)
店员没取出一个面包就通知师傅可以放了,如果店员发现柜子空了,则等待中直到被唤醒(通知)
满了就让师傅等待,空了就让店员等待
java.lang.Object 提供了三个方法:
wait()只能在同步代码中调用,而且只能通过锁对象调用,使当前线程基于所在代码锁等待。
notify() 只能在同步代码中调用,而且只能通过锁对象调用,唤醒【基于当前线程所在代码锁等待的】线程中的一个
notifyAll()只能在同步代码中调用,而且只能通过锁对象调用,唤醒【基于当前线程所在代码锁等待的】所有线程
实现代码:
柜子类
package org.juc.线程协作.线程协作plus;
import lombok.extern.slf4j.Slf4j;
@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.wait();
log.debug("有空位,被唤醒。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size++;
log.debug("放入一个面包,面包数为:{}",size);
this.notifyAll();
}
public synchronized void remove (){
while (this.isEmpty()){
try {
log.debug("柜空,等待中。。。");
this.wait();
log.debug("有面包,被唤醒。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
log.debug("取出一个面包,面包数为:{}",size);
this.notifyAll();
}
}
启动类
package org.juc.线程协作.线程协作plus;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test01 {
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();
}
}
}
synchronized锁升级
偏向锁->轻量级锁->重量级锁
偏向锁:仅有一个线程访问,当有另一个线程访问时升级为轻量级锁
轻量级锁:有多个线程不同时间访问,当多个线程同时访问时产生锁自选
锁的自旋:当一个线程持有锁,另一个线程访问同步代码块时并不阻塞,而是进入循环,等待获取到同步代码块的锁后执行,循环次数大于jvm设置的上限之后升级为重量级锁。(处于运行状态,还在占用cpu,减少在运行态和阻塞态之间切换的消耗)
重量级锁:当锁自旋次数大于jvm设置的上限之后,还没有获得锁的线程进入阻塞状态。
JAVA内存模型(JMM)
多线程把内存分为主内存和工作内存,工作内存是线程独有的
当一个线程从非运行状态变成运行状态时会从主存中更新工作内存的数据
当一个线程拿到锁之后会从主存拿取数据,释放锁的时候将数据向主存回传
线程只要遇到锁,就会从主存中更新工作内存
注:被volatile修饰的变量,主存内的值改变后,立即通知各工作内存从主存中重新读取值。
JMM三大特性
-
原子性-如何保证指令不会受到线程上下文切换的影响。线程上下文切换主要指运行态和阻塞态之间的转换,这个切换消耗成本较大。
-
可见性-如何保证指令不受cpu缓存的影响 (工作内存与主存里的值不一样)
-
有序性-如何保证指令不受cpu指令优化的影响(cpu进行优化时可能会进行指令重排)
synchronized可以保证原子性(锁升级),可见性
volatile可以保证可见性,禁止指令重排(有序性),但不保证原子性(适合一个线程写,多个线程读的情况)
CAS(无锁技术)
CAS即比较并设置,是一种无锁的、非阻塞的线程并发安全技术。
CAS操作:设置新值时,首先将数据的当前值和预期原值进行比较,如果一致则更新为新值,否则 不更新。
CAS和volatile结合实现了安全的原子性并发 在Java中提供的原子数据类型就是这样做的。
线程池ThreadToolExecutor
少量线程对应大量任务,节约资源,效率较高
核心线程
救急线程
阻塞队列
公平锁和非公平锁
公平锁:按照请求锁的顺序依次获得锁,好处是每个线程都有获得锁的机会,效率低一些。
一开始时是线程一处于第一位,线程一执行完之后释放锁,再重新去排队
package org.juc.公平锁和非公平锁;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class 公平锁 {
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();
}
}
class Task implements Runnable {
Lock l = new ReentrantLock(true);//创建一个公平锁
@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();
}
}
}
}
非公平锁:不按照请求顺序获得锁,而是随机顺序获得锁,好处是效率较高,但是有的线程没有获得锁的机会(锁饥饿)。
synchronized是非公平锁
package org.juc.公平锁和非公平锁;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class 非公平锁 {
public static void main(String[] args) {
Task1 task1 = new Task1();
Thread t1 = new Thread(task1,"线程一");
Thread t2 = new Thread(task1,"线程二");
Thread t3 = new Thread(task1,"线程三");
t1.start();
t2.start();
t3.start();
}
}
class Task1 implements Runnable {
Lock l = new ReentrantLock(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();
}
}
}
}
Lock接口
有三个实现类,分别是可重入锁ReentrantLock、读锁ReadLock、写锁writeLock
可重入锁(ReentrantLock)
本线程已经有这个锁之后再获得一次这个锁,获得几次就得释放几次。(Synchronized也是可重入锁。)
可重入锁ReentrantLock(默认非公平锁,可以设置为公平锁或非公平锁)可以完全替代synchronized关键字,并且在扩展功能上也更加强大,而且在使用上也更加灵活。
synchronized与ReentrantLock的区别:
synchronized:无论是否发生异常都自动释放 ReentrantLock等:释放需要手动通过代码释放
读写锁(ReadWriteLock接口)
-
读-读不阻塞
-
读-写阻塞
-
写-写阻塞
创建方式
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();//读锁
Lock writeLock = lock.writeLock();//写锁
Condition接口
在Lock接口的实现类里,属于Object的wait和notify就不能使用了,jdk为我们提供了一个新的方法,即通过Conition接口的await()方法和signal() 方法和signalAll()方法.
创建方式
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
线程协作(读写锁版本)
柜子类
package org.juc.线程协作.读写锁版本;
import lombok.extern.slf4j.Slf4j;
import java.util.Locale;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class Guizi {
private int cap; //容量
private int size;//数量
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();
Condition condition = writeLock.newCondition();
public Guizi(int cap) {
this.cap = cap;
this.size = 0;
}
public boolean isFull() {
readLock.lock();
try {
return size == cap;
} finally {
readLock.unlock();
}
}
public boolean isEmpty() {
readLock.lock();
try {
return size == 0;
} finally {
readLock.unlock();
}
}
public void add() {
writeLock.lock();
try {
while (this.isFull()) {
try {
log.debug("柜满,等待中。。。");
condition.await();
log.debug("有空位,被唤醒。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size++;
log.debug("放入一个面包,面包数为:{}", size);
condition.signalAll();
} finally {
writeLock.unlock();
}
}
public void remove() {
writeLock.lock();
try {
while (this.isEmpty()) {
try {
log.debug("柜空,等待中。。。");
condition.await();
log.debug("有面包,被唤醒。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
log.debug("取出一个面包,面包数为:{}", size);
condition.signalAll();
} finally {
writeLock.unlock();
}
}
}
启动类
package org.juc.线程协作.读写锁版本;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test01 {
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();
try {
Thread.sleep(1); //让它阻塞,释放对cpu的使用权
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "面包师"+k).start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
guizi.remove();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "店员"+k).start();
}
}
}