JUC学习笔记【尚硅谷/周阳】
本文章基于B站视频教程【juc 与 jvm 并发编程 Java 必学_阳哥- 尚硅谷】进行整理记录,仅用于个人学习,交流使用。
目录标题
0.课前引入
1.进程/线程是什么?
进程: 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程: 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
2.进程/线程例子?
使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给c传文件,给D发一段语言,QQ支持录入信息的搜索。
大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。
word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查
1.卖票复习
多线程编程的企业级套路+模板
在高内聚低耦合的前提下,线程 操作 资源类
–>旧版
package lut.juc;
/***
* 买票程序
*
* 多线程编程的企业级套路+模板
* 在高内聚低耦合的前提下,线程 操作(对外暴露的调用方法) 资源类
*/
class Ticket{ //资源类
private int number=30;
public synchronized void saleTicket(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出第"+number--+"张票,还剩"+number);
}
}
}
public class demo01 {
public static void main(String[] args) {
Ticket ticket=new Ticket();
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <=40 ; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <=40 ; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <=40 ; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
–> 改进:线程操作资源类
//资源类 = 实例变量 + 实例方法
package lut.juc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/***
* 买票程序
*
* 多线程编程的企业级套路+模板
* 在高内聚低耦合的前提下,线程 操作(对外暴露的调用方法) 资源类
*/
class Ticket{ //资源类
private int number=30;
private Lock lock=new ReentrantLock(); //可重入锁
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();//释放锁
}
}
}
public class demo01 {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(()->{for (int i = 0; i <=40 ; i++) ticket.saleTicket();},"A").start();
new Thread(()->{for (int i = 0; i <=40 ; i++) ticket.saleTicket();},"B").start();
new Thread(()->{for (int i = 0; i <=40 ; i++) ticket.saleTicket();},"C").start();
}
}
补充: 一文彻底理解ReentrantLock可重入锁的使用
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
2.LambdaExpress
package lut.juc;
/**
* LambdaExpress,不关心方法名
* 口诀:拷贝小括号,写死右箭头,落地大括号
* 接口里面有且只有一个方法,就可以使用,它是函数式接口
*/
@FunctionalInterface//函数式接口注解
interface Foo1{
public void sayHello();
}
@FunctionalInterface//函数式接口注解
interface Foo2{
public int add(int x,int y);
}
@FunctionalInterface//函数式接口注解
interface Foo3{
public int add(int x,int y);
//函数式接口可以有多个 default实现
//JDK1.8之后 接口里面可以有方法的实现
default int div(int x,int y){
System.out.println(x+"/"+y+"="+(x/y));
return x/y;
};
default int mv(int x,int y){
System.out.println(x+"*"+y+"="+(x*y));
return x*y;
};
//可以有多个静态函数
static int div2(int x,int y){
System.out.println(x+"/"+y+"="+(x/y));
return x/y;
};
static int mv2(int x,int y){
System.out.println(x+"*"+y+"="+(x*y));
return x*y;
};
}
public class demo2 {
public static void main(String[] args) {
// Foo foo=new Foo() {
// @Override
// public void sayHello() {
// System.out.println("Hello");
// }
// };
// foo.sayHello();
Foo1 foo1=()->{ System.out.println("hello"); };
foo1.sayHello();
//Foo2 foo2=(int x,int y)->{
Foo2 foo2=(x,y)->{
System.out.println(x+"+"+y+"="+(x+y));
return x*y;
};
foo2.add(10,5);
Foo3 foo3=(x,y)->{
System.out.println(x+"+"+y+"="+(x+y));
return x*y;
};
System.out.println(foo3.add(10,5));
System.out.println(foo3.div(10,5));
System.out.println(foo3.mv(10,5));
System.out.println(Foo3.div2(10,5));
System.out.println(Foo3.mv2(10,5));
}
}
扩展: 【Java基础】Java8新特性—接口中使用default和static关键字
Java 1.8对接口有两个方面的增强:接口中可以添加使用default或者static修饰的方法
增加default方法:又叫做接口扩展方法,即在不破坏java现有实现架构的情况下能往接口里增加新方法, default关键字可以给接口添加一个非抽象的方法实现,子类可以直接调用!
增加static方法: 接口中用static修饰的方法也可以有方法体,和类的静态方法一样,可以通过 接口名.方法名 进行接口中 static方法的调用。
3.生产者消费者
总结:
题目:现在两个线程,可以操作初始值为零的一个变量,
实现一个线程对该变量加1,一个线程对该变量-1,
实现交替,来10轮,变量初始值为0.
1. 高内聚低耦合前提下,线程操作资源类
2. 判断/干活/通知
3. 防止虚假唤醒(判断只能用while,不能用if)
知识小总结:多线程编程套路+while判断+新版写法
题目描述
现在两个线程,可以操作初始值为零的一个变量,
实现一个线程对该变量加1,一个线程对该变量-1,
实现交替,来10轮,变量初始值为0.
旧版写法:
package lut.juc;
/**
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量-1,
* 实现交替,来10轮,变量初始值为0.
* 1. 高内聚低耦合前提下,线程操作资源类
* 2. 判断/干活/通知
* 3. 防止虚假唤醒(判断只能用while,不能用if)
* 知识小总结:多线程编程套路+while判断+新版写法
*/
class Airconditioner {
private int number = 0;
public synchronized void increment() throws InterruptedException {
//1.判断
if (number != 0) {
this.wait();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName() + "~" + number);
//3.通知
this.notify();
}
public synchronized void decrement() throws InterruptedException {
//1.判断
if (number == 0) {
this.wait();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName() + "~" + number);
//3.通知
this.notify();
}
}
public class demo03 {
public static void main(String[] args) {
Airconditioner airconditioner=new Airconditioner();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
需求变更:
要求使用四个多线程(两个加,两个减)来模拟对数值的操作
方案:直接增加多线程为4个
new Thread(()->{......},"A").start();
new Thread(()->{......},"B").start();
new Thread(()->{......},"C").start();
new Thread(()->{......},"D").start();
出现的问题:在运行过程中会出现 0,1,2,3 等多种情况
改进:将判断条件 if 修改为 while
原理:防止虚假唤醒(判断只能用while,不能用if)
package lut.juc;
/**
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量-1,
* 实现交替,来10轮,变量初始值为0.
* 1. 高内聚低耦合前提下,线程操作资源类
* 2. 判断/干活/通知
* 3. 多线程中交互中 必须要,防止多线程的虚假唤醒(判断只能用while,不能用if)
* 知识小总结:多线程编程套路+while判断+新版写法
*/
class Airconditioner {
private int number = 0;
public synchronized void increment() throws InterruptedException {
//1.判断
while(number != 0) {
this.wait();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName() + "~" + number);
//3.通知
this.notify();
}
public synchronized void decrement() throws InterruptedException {
//1.判断
while (number == 0) {
this.wait();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName() + "~" + number);
//3.通知
this.notify();
}
}
public class demo03 {
public static void main(String[] args) {
Airconditioner airconditioner=new Airconditioner();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
airconditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
新版写法:
使用 lock 替换 synchronized
package lut.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量-1,
* 实现交替,来10轮,变量初始值为0.
* 1. 高内聚低耦合前提下,线程操作资源类
* 2. 判断/干活/通知
* 3. 多线程中交互中 必须要,防止多线程的虚假唤醒(判断只能用while,不能用if)
* 知识小总结:多线程编程套路+while判断+新版写法
*/
class Airconditioner {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
while (number != 0) {
condition.await();//this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "~" + number);
condition.signalAll();//this.notify();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
try {
lock.lock();
while (number == 0) {
condition.await();//this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "~" + number);
condition.signalAll();//this.notify();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class demo03 {
public static void main(String[] args) {
Airconditioner airconditioner = new Airconditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airconditioner.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airconditioner.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airconditioner.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airconditioner.decrement();
}
}, "D").start();
}
}
在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
1. 使用Object的wait() / notify()方法
2. 使用Lock和Condition的await() / signal()方法
3. 使用BlockingQueue阻塞队列方法
4. PipedInputStream / PipedOutputStream
本文只介绍最常用的前三种,第四种暂不做讨论,
4.精确通知顺序访问
总结
/**
* 题目:多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* 来10轮
*
*
* 1.高内聚低耦合前提下,线程操作资源类
* 2.判断/干活/通知
* 3.多线程交互中,防止虚假唤醒(判断只能用while,不能用if)
* 4.标志位
*/
题目描述
多线程之间按顺序调用,实现A->B->C
三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
接着
AA打印5次,BB打印10次,CC打印15次
来10轮
package lut.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData{
private int number=1;
private Lock lock=new ReentrantLock();
private Condition c1=lock.newCondition();
private Condition c2=lock.newCondition();
private Condition c3=lock.newCondition();
public void print5(){
lock.lock();
try {
//1.判断
while (number!=1){
c1.await();
}
//2.干活
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"~"+(i+1));
}
//3.通知
number=2;
c2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//1.判断
while (number!=2){
c2.await();
}
//2.干活
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"~"+(i+1));
}
//3.通知
number=3;
c3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
//1.判断
while (number!=3){
c3.await();
}
//2.干活
for (int i = 0; i <15 ; i++) {
System.out.println(Thread.currentThread().getName()+"~"+(i+1));
}
//3.通知
number=1;
c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class demo04 {
public static void main(String[] args) {
ShareData shareData=new ShareData();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareData.print5();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareData.print10();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareData.print15();
}
},"C").start();
}
}
5.八锁理论
总结:
①一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。
②锁的是当前对象this,被锁定后,其他线程都不能进入到当前对象的其他的synchronized方法。
③加个普通方法后发现和同步锁无关。
④换成静态同步方法后,情况又变化
⑤所有的非静态同步方法用的都是同一把锁 -- 实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已经取锁的非静态同步方法释放锁就可以获取他们自己的锁。
⑥所有的静态同步方法用的也是同一把锁 -- 类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个实例对象
八锁现象
class Phone{
public static synchronized void sendEmail() throws Exception{
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("*******sendEmail");
}
public synchronized void sendMs() throws Exception{
TimeUnit.SECONDS.sleep(2);
System.out.println("*******sendMs");
}
public void sayHello() throws Exception{
TimeUnit.SECONDS.sleep(3);
System.out.println("*****sayHello");
}
}
/**
* 1.标准访问,先打印邮件
* 2.邮件设置暂停4秒方法,先打印邮件
* 对象锁
* 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
* 其他的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法,
* 锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法
* 3.新增sayHello方法,先打印sayHello
* 加个普通方法后发现和同步锁无关
* 4.两部手机,先打印短信
* 换成两个对象后,不是同一把锁了,情况立刻变化
* 5.两个静态同步方法,同一部手机,先打印邮件
* 6.两个静态同步方法,同两部手机,先打印邮件,锁的同一个字节码对象
* 全局锁
* synchronized实现同步的基础:java中的每一个对象都可以作为锁。
* 具体表现为一下3中形式。
* 对于普通同步方法,锁是当前实例对象,锁的是当前对象this,
* 对于同步方法块,锁的是synchronized括号里配置的对象。
* 对于静态同步方法,锁是当前类的class对象
* 7.一个静态同步方法,一个普通同步方法,同一部手机,先打印短信
* 8.一个静态同步方法,一个普通同步方法,同二部手机,先打印短信
* 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
* 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁,
* 可是别的实例对象的普通同步方法因为跟该实例对象的普通同步方法用的是不同的锁,
* 所以无需等待该实例对象已获取锁的普通同步方法释放锁就可以获取他们自己的锁。
*
* 所有的静态同步方法用的也是同一把锁--类对象本身,
* 这两把锁(this/class)是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有静态条件的。
* 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,
* 而不管是同一个实例对象的静态同步方法之间,
* 还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象
*/
public class LockBDemo05 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendMs();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sayHello();
} catch (Exception e) {
e.printStackTrace();
}
},"c").start();
}
}
6.集合不安全类
List
java.util.ConcurrentModificationException 并发修改异常
package lut.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class demo06 {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
/***
java.util.ConcurrentModificationException 并发修改异常
当 i<3 使,系统正常运行,但每次结果不一样
***/
1.new Vector<>();
public class demo06 {
public static void main(String[] args) {
List<String> list=new Vector<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
/***
保证了数据一致性,加锁没有保证并发性
**/
2.Collections.synchronizedList(new ArrayList());
Q:collection 和 collections 的区别
public class demo06 {
public static void main(String[] args) {
List<String> list= Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
3.new CopyOnWriteArrayList(); //写时复制
public class demo06 {
public static void main(String[] args) {
List<String> list= new CopyOnWriteArrayList();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码:CopyOnWriteArrayList.java
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
Set
Q:hashset 的底层结构是什么
A:hashmap
Q:HashMap 是 key-value 的形式,为什么 hash只存放一个数
A:
/*hashset.java 源码*/
public boolean add(E e) { return map.put(e, PRESENT)==null; }
private static final Object PRESENT = new Object();//常量
java.util.ConcurrentModificationException
public class demo06 {
public static void main(String[] args) {
Set<String> set=new HashSet<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
public class demo06 {
public static void main(String[] args) {
Set<String> set=new CopyOnWriteArraySet<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
Map
public class demo06 {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();//new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
总结
/**
* 1.故障现象
* 并发修改异常
* java.util.ConcurrentModificationException
* 2.导致原因
* 3.解决方法
* 3.1 new Vector<>();
* 3.2 Collections.synchronizedList(new ArrayList<String>());
* 3.3 new CopyOnWriteArrayList(); //写时复制
* 4.优化建议(同样的错误不犯第二次)
*
* 写时复制:
* CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是现将当前容器Object[]进行Copy,
* 复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,
* 再将原容器的引用指向新的容器setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,
* 而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
*/
7.Callable
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return null;
}
}
Q: callable接口与runnable接口的区别?
A:(1)是否有返回值,(2〉是否抛异常,(3)落地方法不一样,一个是run,一个是call
Q:在Java中实现多线程有哪三种方法
A:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。
实现多线程第三种方式:Callable 接口
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("hello----1024");
return 1024;
}
}
public class demo07 {
public static void main(String[] args) throws Exception {
MyThread2 myThread2=new MyThread2();
//Thread t1=new Thread(myThread2);---> Error
//FutureTask作为桥梁连接 Thread 和 Callable
FutureTask futureTask=new FutureTask(new MyThread2());
new Thread(futureTask,"A").start();
System.out.println(futureTask.get());//获取返回值
//get放在最后一行是为了计算完再获取值
}
}
扩展:
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("hello----1024");
TimeUnit.SECONDS.sleep(4);
return 1024;
}
}
public class demo07 {
public static void main(String[] args) throws Exception {
MyThread2 myThread2=new MyThread2();
//Thread t1=new Thread(myThread2);---> Error
//FutureTask作为桥梁连接 Thread 和 Callable
FutureTask futureTask=new FutureTask(new MyThread2());
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
System.out.println(futureTask.get());//获取返回值
}
}
调用一次:计算完之后放在缓存,下次用直接拿出
hello----1024
1024
扩展:Java泛型通配符
java泛型的通配符,其实换成任何字母结果都是一样的,不过约定俗称按照一定的含义就选用这几个字母进行表示了。
E - Element (在集合中使用,因为集合中存放的是元素) Collection
T - Type(Java 类) public T Test1(T t){}
K - Key(键) Map<K,V>
V - Value(值)
N - Number(数值类型)List
? - 表示不确定的java类型 List<? super Integer> intgerList;
8.CountDownLatch
常理来说:其他线程结束之后,主线程才结束
存在的问题演示
public class demo08 {
public static void main(String[] args) {
for (int i = 0; i <6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"~离开教室");
},String.valueOf(i)).start();
}
System.out.println("班长关门");
}
}
/**
0~离开教室
2~离开教室
3~离开教室
5~离开教室
班长关门
1~离开教室
4~离开教室
**/
使用CountDownLatch
public class demo08 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 0; i <6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"~离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("班长关门");
}
}
/**
0~离开教室
2~离开教室
1~离开教室
3~离开教室
4~离开教室
5~离开教室
班长关门
*/
/*
* CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
* 其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
* 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
*/
9.CyclicBarrier
集齐七颗龙珠召唤神龙
public class demo09 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("**召唤神龙**");
});
for (int i = 0; i <7 ; i++) {
final int tempInt=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"找到第"+tempInt+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
/*
0找到第0颗龙珠
1找到第1颗龙珠
2找到第2颗龙珠
3找到第3颗龙珠
6找到第6颗龙珠
4找到第4颗龙珠
5找到第5颗龙珠
**召唤神龙**
*/
10.Semaphore
主要用于资源的并发控制
当 new Semaphore(1) 时相当于 synchronized
Demo:synchronized
public class demo10 {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);//三个空车位
for (int i = 0; i <7; i++) { //七个车抢资源
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到了车位");
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
/**
1抢到了车位
2抢到了车位
0抢到了车位
0离开了车位
1离开了车位
2离开了车位
3抢到了车位
5抢到了车位
4抢到了车位
3离开了车位
4离开了车位
6抢到了车位
5离开了车位
6离开了车位
**/
在信号量上我们定义两种操作:
* acquire(获取)当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1)
要么一直等下去,直到有线程释放信号量,或超时。
* release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
*信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制心
11.ReadWriteLock
/**
* 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
* 但是,如果有一个线程想去写共享资源来,就不应该再有其他线程可以对改资源进行读或写
* 小总结:
* 读-读能共存
* 读-写不能共存
* 写-写不能共存
*/
存在的问题:当有一个写入操作时,又有其他的操作插进来,破坏了事务的原子性
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入数据key" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成key" + key);
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取数据key" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成key" + key);
}
}
public class demo11 {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
}
/**
2写入数据key2
3写入数据key3
3写入完成key3
5写入数据key5
5写入完成key5
1写入数据key1
4写入数据key4
1写入完成key1
2写入完成key2
4写入完成key4
1读取数据key1
1读取完成key1
3读取数据key3
3读取完成key3
2读取数据key2
2读取完成key2
4读取数据key4
4读取完成key4
5读取数据key5
5读取完成key5
**/
加入读写锁
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();//写锁
try {
System.out.println(Thread.currentThread().getName() + "写入数据key" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成key" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取数据key" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成key" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
}
public class demo11 {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
}
/**
2写入数据key2
2写入完成key2
5写入数据key5
5写入完成key5
3写入数据key3
3写入完成key3
1写入数据key1
1写入完成key1
4写入数据key4
4写入完成key4
1读取数据key1
1读取完成key1
2读取数据key2
2读取完成key2
3读取数据key3
3读取完成key3
4读取数据key4
4读取完成key4
5读取数据key5
5读取完成key5
**/
12.BlockingQueue
BlockingQueue 阻塞队列 种类
当队列是空的,从队列中获取元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元幸
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
BlockingQueue API
核心方法
package lut.juc;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class demo12 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
/* 第一组:报异常
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.add("d"));//多添加会报异常
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());//删除多会报异常
*/
/* 第二组:false null
System.out.println(blockingQueue.offer("x"));
System.out.println(blockingQueue.offer("y"));
System.out.println(blockingQueue.offer("z"));
System.out.println(blockingQueue.offer("o"));//false
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null
*/
/*第三组:阻塞
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
// blockingQueue.put("a");//阻塞//卡住
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());//卡住
*/
//第四组
System.out.println(blockingQueue.offer("x"));
System.out.println(blockingQueue.offer("y"));
System.out.println(blockingQueue.offer("z"));
System.out.println(blockingQueue.offer("a",3L, TimeUnit.SECONDS));//等待三秒,过时不候//false
}
}
13.TransferValue
@NoArgsConstructor
@Getter
@Setter
public class Person {
private Integer id;
private String personname;
public Person(String personname){
this.personname=personname;
}
}
public class TestTransferValue {
public void changeValue1(int age){
age = 30;
}
public void changeValue2(Person person){
person.setPersonName("xxx");
}
public void changeValue3(String str){
str = "xxx";
}
public static void main(String[] args) {
TestTransferValue test = new TestTransferValue();
int age = 20;
test.changeValue1(age);
System.out.println("age----"+age);//20
Person person = new Person("abc");
test.changeValue2(person);
System.out.println("personName-------"+person.getPersonName());//xxx
String str = "abc";
test.changeValue3(str);
System.out.println("String-----"+str);//abc
}
}
14.线程池
线程池的好处
少new 且能复用
例子:10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换。
现在是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。
线程池的优势:线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的三大方法
public class demo13 {
public static void main(String[] args) {
ExecutorService threadpool1= Executors.newFixedThreadPool(5);//五个受理窗口//一池五线程
ExecutorService threadpool2= Executors.newSingleThreadExecutor();//一池一线程
ExecutorService threadpool3= Executors.newCachedThreadPool();//一池N线程,自动扩展
try {
for (int i = 0; i <=10 ; i++) { //10个客户
threadpool1.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadpool1.shutdown();
}
}
}
/*
pool-1-thread-1 办理业务
pool-1-thread-3 办理业务
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-1 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-2 办理业务
pool-1-thread-5 办理业务
*/
15.ThreadPoolExecutor底层原理
16.线程池里7大参数
1、corePoolSize:线程池中的常驻核心线程数
2、maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
3、keepAliveTime:多余的空闲线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
4、unit: keepAliveTime的单位
5、workQueue:任务队列,被提交但尚未被执行的任务
6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
7、handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略
/*源码*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
17.线程池底层工作原理
线程池工作原理
银行demo演示
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoo1lSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
面试题
为什么不用?
自定义线程池
public class demo14 {
public static void main(String[] args) {
ExecutorService threadpool = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy());//RejectedExecutionException//默认
// new ThreadPoolExecutor.CallerRunsPolicy());//main 办理业务,回退给调用者
// new ThreadPoolExecutor.DiscardPolicy());//抛弃,允许丢失
new ThreadPoolExecutor.DiscardOldestPolicy());//抛弃等待最久的,允许丢失
try {
for (int i = 0; i <= 10; i++) { //10个客户
threadpool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadpool.shutdown();
}
}
}
四大拒绝策略
扩展
maximumPoolSize=CPU核数+1
public class demo14 {
public static void main(String[] args) {
int t=Runtime.getRuntime().availableProcessors();
ExecutorService threadpool = new ThreadPoolExecutor(
2,
t+1,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy());//RejectedExecutionException//默认
// new ThreadPoolExecutor.CallerRunsPolicy());//main 办理业务,回退给调用者
// new ThreadPoolExecutor.DiscardPolicy());//抛弃,允许丢失
new ThreadPoolExecutor.DiscardOldestPolicy());//抛弃等待最久的,允许丢失
try {
for (int i = 0; i <= 10; i++) { //10个客户
threadpool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadpool.shutdown();
}
}
}
18.四大函数式接口
链式编程+流式计算
函数式接口
Package java.util.function
🛩 函数式型接口 Function<T,R>
传入一个参数,获取一个返回值
Function简单使用
Function<String,Integer> function=new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return 1024;
}
};
System.out.println(function.apply("abc"));
lambda结合方法引用
// Function<String,Integer> function=(String s)->{ return s.length(); };
Function<String,Integer> function=s->{ return s.length(); };
System.out.println(function.apply("abc"));
🛩 断定型接口 Predicate<T>
传入一个参数,返回一个 bool 值
// Predicate<String> predicate=new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// }
Predicate<String> predicate=s->{return s.isEmpty();};
System.out.println(predicate.test("abc"));
🛩 消费型接口 Consumer<T>
传入参数,没有返回值
Consumer<String> consumer=new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
Consumer consumer1=s->{ System.out.println(s); };
consumer1.accept("ssss");
🛩 供给型接口 Supplier<T>
没有传参数,有返回值
Supplier<String> supplier=new Supplier<String>() {
@Override
public String get() {
return "java";
}
};
Supplier<String> supplier1=()->{return "java";};
System.out.println(supplier1.get());
19.Stream流式计算
📑 流(Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”
Demo01
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private int id;
private String name;
private int age;
}
/**
* 题目:请按照给出数据,找出同时满足
* 偶数ID
* 且年龄大于24
*/
public class demo16 {
public static void main(String[] args) {
User u1=new User(11,"A",23);
User u2=new User(12,"B",24);
User u3=new User(13,"C",22);
User u4=new User(14,"D",28);
User u5=new User(16,"E",27);
List<User> list= Arrays.asList(u1,u2,u3,u4,u5);
// list.stream().filter(u -> {return u.getId()%2==0;}).forEach(System.out::println);
list.stream().filter(u -> {return u.getId()%2==0;})
.filter(user -> {return user.getAge()>24;})
.forEach(System.out::println);
}
}
Demo02
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private int id;
private String name;
private int age;
}
/**
* 题目:请按照给出数据,找出同时满足
* 偶数ID
* 且年龄大于24
* 且用户名转为大写
* 且用户名字母倒排序
* 最后只输出一个用户名字
*/
public class demo16 {
public static void main(String[] args) {
User u1=new User(11,"a",23);
User u2=new User(12,"c",24);
User u3=new User(13,"c",22);
User u4=new User(14,"d",28);
User u5=new User(16,"e",27);
List<User> list= Arrays.asList(u1,u2,u3,u4,u5);
// list.stream().filter(u -> {return u.getId()%2==0;}).forEach(System.out::println);
list.stream().filter(u -> {return u.getId()%2==0;})
.filter(user -> {return user.getAge()>24;})
.map(m->{return m.getName().toUpperCase();})
.sorted((o1, o2) -> {return o2.compareTo(o1);})
.limit(1)
.forEach(System.out::println);
}
}
20.ForkJoin
class MyTask extends RecursiveTask<Integer> {
private static final Integer ADJUST_VALUE = 10;
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if ((end - begin) <= ADJUST_VALUE) {
for (int i = begin; i <= end; i++) {
result = result + i;
}
} else {
int middle = (end + begin) / 2;
MyTask task01 = new MyTask(begin, middle);
MyTask task02 = new MyTask(middle + 1, end);
task01.fork();
task02.fork();
result = task01.join() + task02.join();
}
return result;
}
}
/**
* 分支合并框架
* ForkJoinPool
* ForkJoinTask
* RecursiveTask
*/
public class demo18 {
public static void main(String[] args) throws Exception {
MyTask myTask = new MyTask(0, 100);
ForkJoinPool threadPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = threadPool.submit(myTask);
System.out.println(forkJoinTask.get());
threadPool.shutdown();
}
}
21.CompletableFuture
public class demo17 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+" 没有返回,update mysql ok ");
});
completableFuture.get();
//异步回调
CompletableFuture<Integer> completableFuture1=CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+" completableFuture1 ");
// int a=10/0;
return 1024;
});
System.out.println(completableFuture1.whenComplete((t, u) -> {
System.out.println("**t:" + t);
System.out.println("**u:" + u);
}).exceptionally(f -> {
System.out.println("**exception:" + f.getMessage());
return 4444;
}).get());
}
}
时间:2021年4月
作者:耿鬼不会笑