三天回顾Java——零基础速成实战
第二天 初识Java入门基础(下)
11.网络编程
11.1、概述11.2、**网络通信的要素**11.3、IP11.4、端口11.5、通信协议
11.5.1**TCP UDP 对比** 11.6、TCP
11.6.1文件上传11.6.2Tomcat 11.7、DUP
11.7.1发送接收消息11.7.2多线程发送接收消息 11.8、URL 12、注解和反射
12.1、注解概念12.2:内置注解
12.2.1、内置注解 12.3:什么是元注解
12.3.1、元注解 12.4:自定义注解
12.4.1、自定义注解 12.5:反射概述
12.5.1、反射概念 12.6:获取反射对象
12.6.1、反射机制提供的功能12.6.2、反射相关的主要API12.6.3、反射初体验12.7.1:得到Class类的几种方式 13、JUC详细学习
13.1、什么是JUC13.2、线程和进程
13.2.1**并发、并行**13.2.2线程有几个状态?13.2.3.**wait/sleep的区别** 13.3、Lock锁(重点)
13.3.1.Lock接口**13.3.2.Synchronized 和 Lock区别**13.2.3、生产者和消费者问题!13.2.4、八锁现象13.2.5小结 13.4、集合类不安全
13.4.1、List不安全13.4.2、Set不安全13.4.3、Map不安全 13.5、Callable(简单)13.6、常用的辅助类(必会!)
13.6.1 CountDownLatch13.6.2 CyclickBarrier13.6.3 Semaphore 13.7、读写锁13.8、阻塞队列
**13.8.1四组API** 13.9.SynchronousQueue同步队列13.10、线程池(重点)13.11、四大函数式接口(必需掌握)
13.11.1**函数式接口:**13.11.2、Stream流式计算13.11.3链式编程 ===13.11.4,lambda表达式 13.12、ForkJoin13.13、异步回调13.14.JMM13.15、Volatile
13.15.1.保证可见性13.15.2.**不保证原子性****13.15.3、禁止指令重排** 13.16、玩转单例模式
13.16.1.饿汉式、DCL懒汉式13.16.2枚举 13.17、深入理解CAS13.18、原子引用13.19、各种锁的理解
13.19.1、公平锁、非公平锁13.19.2、可重入锁13.19.3.lock锁13.19.4、自旋锁13.19.5.自我设计自旋锁:13.19.6死锁 》》》完结撒花
第二天 初识Java入门基础(下) 11.网络编程InetAddress 获取地址 InetAddress.getCanonicalHostName 规范的名字 InetAddress.getHostAddress IP InetAddress.getHostName 域名或自己电脑的名字 InetSocketAddress 实现 IP 地址及端口 InetAddress 实现 IP 地址 ServerSocket 建立服务的端口 .accept 阻塞监听等待连接 Socket 创建连接 .getInputStream 获取IO输入流 .getOutputStream 获取IO输出流 ByteArrayOutputStream byte类型数组管道输出流 FileOutputStream 文件字符输出流 FileInputStream 文件字符输入流 shutdownOutput 停止输出 DatagramSocket 数据包端口 DatagramPacket 数据包 .send 发送 .receive 阻塞接收 BufferedReader 缓存区读取 InputStreamReader 输入流读取 .readLine 读取的一行内容 URL 统一资源定位符 .openConnection 打开连接 HttpURLConnection 指定协议HTTP11.1、概述
计算机网络
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
11.2、网络通信的要素如何实现网络的通信?
通信双方地址:
ip端口号192.168.16,124:5900
规则:网络通信的协议
http, ftp, smtp, tcp,udp…
TCP/IP参考模型:
小结:
1.网络编程中有两个主要的问题
如何准确的定位到网络上的一台或者多台主机
找到主机之后如何进行通信
2.网络编程中的要素
IP 和 端口号 IP网络通信协议 udp,tcp
3.万物皆对象
IP和网络通信协议都可以生成对象 11.3、IP
ip地址:InetAddress
唯一定位一台网络上计算机
127.0.0.1:本机Iocalhost
ip地址的分类
ipv4/ipv6
IPV4:127.0.01 ,4个字节组成,0~255,总共42亿;30亿都在北美,亚洲4亿,2011年就已经用尽IPV6:128位。8个无符号整数。2001:acca:0ac1:0002:0ab7:1153:2210:ccc1
公网(互联网)-私网(局域网)
ABCD类地址
192.168.xxx.xx 专门给组织内部使用
- 域名:记忆IP问题
IP:www.xxx.com 方便记录
InetAddress 获取地址 InetAddress.getCanonicalHostName 规范的名字 InetAddress.getHostAddress IP InetAddress.getHostName 域名或自己电脑的名字11.4、端口
端口表示计算机上的一个程序的进程;
不同的进程有不同的端口!用来区分软件!
被规定0~65535,不能使用相同的端口
TCP,UDP:65535*2,tcp : 80, udp : 80 这样不影响。单个协议下,端口号不能冲突
端口分类
公有端口 0~1023 (尽量不用)
HTTP:80HTTPS:443FTP:21Telent:23 程序注册端口:1024~49151,分配用户或者程序
Tomcat:8080MySQL:3306Oracle:1521 动态、私有:49152~65535(尽量不用)
netstat -ano #查看所有的端口 netstat -ano|findstr "5900" #查看指定的端口 tasklist|findstr "8696" #查看指定端口的进程
找到电脑上特定端口,或有其对应的处理程序,才能收到发出的程序
InetSocketAddress IP 地址及端口 InetAddress IP 地址11.5、通信协议
协议:约定,双方使用相同可识别的语言
**网络通信协议:**速率、传输码率、代码结构、传输控制… …
主要使用:**TCP/IP协议簇:**实际上是一组协议
主要:
TCP:用户传输协议 {类似于打电话,需要两边进行连接}UDP:用户数据报协议 {类似于发短信,不需要两边连接也可以发出,但不一定能送到}
出名的协议:
TCP:IP:网络互连协议 11.5.1TCP UDP 对比
- TCP:打电话
连接、稳定三次握手 四次挥手
最少需要三次,保证稳定连接 A—— 我要连接 ——>B B—— 你可以连接 ——>A A—— 那我连接了 ——>B 连接成功! 四次挥手 A——我要断开——>B B——你可以断开——>A B——你确定断开?——>A A——我确定断开!——>B 连接断开
客户端、服务端:主动和被动的过程传输完成,释放连接,效率低
- UDP:发短信
不连接、不稳定客户端、服务端:没有明确的界限不管有没有准备好,都可以发给你DDOS:洪水攻击!(饱和攻击)
服务器
建立服务的端口 ServerSocket等待用户的连接 accept接收用户的信息 11.6.1文件上传
服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpClientDemo02 {
public static void main(String[] args) throws IOException {
//1.创建服务
ServerSocket serverSocket = new ServerSocket(9000);
//2.监听客户端的连接
System.out.println("等待连接");
Socket socket = serverSocket.accept(); //阻塞式监听,会一直等待客户端连接
//3.获取输入流
InputStream is = socket.getInputStream();
//文件输出
FileOutputStream fos = new FileOutputStream(new File("666.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//服务器端接收完毕,并回复信息
OutputStream os = socket.getOutputStream();
os.write("我接收完毕,你可以断开".getBytes());
//关闭资源
os.close();
fos.close();
is.close();
socket.close();
serverSocket.close();
}}
客户端
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class TcpServerDemo02 {
public static void main(String[] args) throws Exception {
//1.创建一个Socket连接
// Socket(主机--端口号)
//InetAddress.getByNam(主机--IP地址)
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
//2.创建一个输出流
OutputStream os = socket.getOutputStream();
//3.读取文件
FileInputStream fis = new FileInputStream(new File("321.jpg"));
//4.写出文件
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//客户端已经传输完毕
socket.shutdownOutput();
//接收服务端完毕信息
InputStream inputStream = socket.getInputStream();
//由于收到的式String byte[]数组,使用byte输出管道流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while((len2 = inputStream.read(buffer))!=-1){
baos.write(buffer,0,len2);
}
System.out.println(baos.toString());
//关闭资源
baos.close();
inputStream.close();
fis.close();
os.close();
socket.close();
}}
FileOutputStream 文件输出流 FileInputStream 文件输入流 shutdownOutput 停止输出11.6.2Tomcat
服务端
自定义 STomcat 服务器 S :Java 后台开发!
客户端
自定义 C浏览器 B 11.7、DUP
发短信:不用连接,只需要知道对方的地址!
11.7.1发送接收消息 11.7.2多线程发送接收消息发送线程
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class TalkSend implements Runnable{
DatagramSocket socket = null;
BufferedReader reader = null;
private int fromPort;
private String toIP;
private int toPort;
public TalkSend(int fromPort, String toIP, int toPort) {
this.fromPort = fromPort;
this.toIP = toIP;
this.toPort = toPort;
try {
socket = new DatagramSocket(fromPort);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while(true){
//准备数据:控制台读取 System.in
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//发包内数据
String data = reader.readLine();
byte[] datas = data.getBytes();
DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toIP,this.toPort));
//发送包
socket.send(packet);
if (data.equals("bye")){
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
//关闭流
socket.close();
}
}
接收线程
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive implements Runnable{
DatagramSocket socket =null;
private int port;
private String msgfrom;
public TalkReceive(int port, String msgfrom) {
this.port = port;
this.msgfrom = msgfrom;
try {
socket = new DatagramSocket(port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while(true){
//准备接收包裹
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container,0,container.length);
//阻塞式接收
socket.receive(packet);
//断开连接
//将接收包转换为 String 格式
byte[] data = packet.getData();
String receiveData = new String(data,0,data.length);
System.out.println(msgfrom+":"+receiveData);
if (receiveData.equals("bye")){
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
socket.close();
}
}
学生线程
public class TalkStudent {
public static void main(String[] args) {
//学生同时开启两个线程
//学生自己的发送端口、接收IP、接收端口
new Thread(new TalkSend(6666,"localhost",9999)).start();
//学生自己接收端口、发送过来者
new Thread(new TalkReceive(8888,"老师")).start();
}
}
教师线程
package com.ssxxz.chat;
public class TalkTeacher {
public static void main(String[] args) {
//老师
//老师自己的发送端口、接收IP、接收端口
new Thread(new TalkSend(7777,"localhost",8888)).start();
//老师自己接收端口、发送过来者
new Thread(new TalkReceive(9999,"学生")).start();
}
}
11.8、URL
https://www.baidu.com/
统一资源定位符:定位资源的,定位互联网上的某一个资源
DNS 域名解析:www.baidu.com == xxx.x…x…x 的IP号
协议:(https) //ip地址:端口/项目名/资源
URL常用方法
public class DRLDemo01 {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=kuangshen&password==123");
//协议http
System.out.println(url.getProtocol());
//主机ip,localhost
System.out.println(url.getHost());
//端口,8080
System.out.println(url.getPort());
//文件,/helloworld/index.jsp
System.out.println(url.getPath());
//全路径,/helloworld/index.jsp?username=kuangshen&password==123
System.out.println(url.getFile());
//参数,username=kuangshen&password==123
System.out.println(url.getQuery());
}
}
URL网络下载
public class UrlDown {
public static void main(String[] args) throws Exception {
//1.下载地址
URL url = new URL("https:/**
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
**这是一个C++底层,Java是没有权限操作底层硬件的**
private native void start0();
Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
13.2.1并发、并行并发: 多线程操作同一个资源。
CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人一起行走
CPU多核,多个线程可以同时执行。 我们可以使用线程池!
public class Test1 {
public static void main(String[] args) {
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
- 并发编程的本质:充分利用CPU的资源!
线程的状态:6个状态
public enum State {
//运行
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
13.2.3.wait/sleep的区别
来自不同的类
wait => Object类 sleep => Thread类
一般情况企业中使用休眠是:
TimeUnit.DAYS.sleep(1); //休眠1天 TimeUnit.SECONDS.sleep(1); //休眠1s
关于锁的释放
wait 会释放锁;
sleep睡觉了,不会释放锁;
使用的范围是不同的
wait 必须在同步代码块中;
sleep 可以在任何地方睡;
是否需要捕获异常
wait是不需要捕获异常;
sleep必须要捕获异常;
13.3、Lock锁(重点)传统的Synchronized
public class SaleTicketDemo01 {
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;
//卖票的方式
// synchronized 本质:队列,锁
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
number--;
}
}
13.3.1.Lock接口
公平锁: 十分公平,必须先来后到~;
非公平锁: 十分不公平,可以插队;(默认为非公平锁)
public class SaleTicketDemo02 {
public static void main(String[] args) {
//多线程操作
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
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();
}
}
//lock三部曲
//1、 Lock lock=new ReentrantLock();
//2、 lock.lock() 加锁
//3、 finally=> 解锁:lock.unlock();
class Ticket2{
private int number=50;
Lock lock=new ReentrantLock();
//卖票的方式
// 使用Lock 锁
public void sale(){
//加锁
lock.lock();
try {
//业务代码
if(number>=0){
System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
number--;
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
//解锁
lock.unlock();
}
}
}
13.3.2.Synchronized 和 Lock区别
Synchronized 内置的Java关键字,Lock是一个Java类
Synchronized 无法判断获取锁的状态,Lock可以判断
Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
Synchronized 线程1(获得锁->阻塞)、线程2(等待);
lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
13.2.3、生产者和消费者问题!我们这次使用lock版本
Synchronized版本wait notify可以实现,该方法是传统版本;
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();
}
}
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线程,现在如果我有四个线程A B C D!
//解决方案: if 改为while即可,防止虚假唤醒
//这样就不存在问题了:
JUC版本的生产者和消费者问题
await、signal 替换 wait、notify
通过Lock找到Condition
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{for(int i=0;i<10;i++) {
data.increment();
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.decrement();
}},"B").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.increment();
}
},"C").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.decrement();
}
},"D").start();
}
}
class Data2{
//数字 资源类
private int number = 0;
//lock锁
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment() {
lock.lock();
try{
//业务
while (number!=0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() {
lock.lock();
try{
//业务
while (number==0){
//等待操作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition的优势:精准的通知和唤醒的线程!
如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printA();
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printB();
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printC();
}
},"C").start();
}
}
class Data3{
//资源类
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //1A 2B 3C
public void printA(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=1){
//等待
condition1.await();
}
//操作
System.out.println(Thread.currentThread().getName()+",AAAAA");
//唤醒指定的线程
number=2;
condition2.signal(); // 唤醒2
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+",BBBBB");
//唤醒3
number=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+",CCCCC");
//唤醒1
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
13.2.4、八锁现象
即为8个常见的锁现象
- 如何判断锁的是谁!锁到底锁的是谁?
锁会锁住:对象、Class
深刻理解我们的锁
问题1:
结果是:先发短信,如何再打电话!
为什么? 如果你认为是顺序在前? 这个答案是错误的!
问题2:
我们再来看:我们让发短信 延迟4s
现在结果是什么呢?
结果:还是先发短信,然后再打电话!
why?
原因:并不是顺序执行!是因为synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行!另外一个则等待!
问题3:
如果我们添加一个普通方法,那么先执行哪一个呢?
答案是:先执行hello,然后再执行发短信!原因是hello是一个普通方法,不受synchronized锁的影响,但是我发现,如果我把发短信里面的延迟4秒去掉,那么就会顺序执行,先执行发短信然后再执行hello,原因应该是顺序执行的原因吧,不是太理解。
问题4:
如果我们使用的是两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?
答案是:先打电话,后发短信。原因:在发短信方法中延迟了4s,又因为synchronized锁的是对象,但是我们这使用的是两个对象,所以每个对象都有一把锁,所以不会造成锁的等待。正常执行
问题5,6:
如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?
(1)我们先来使用一个对象调用两个方法!
答案是:先发短信,后打电话
(2)如果我们使用两个对象调用两个方法!
答案是:还是先发短信,后打电话
原因是什么呢? 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?
原因是:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!
问题7:
如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?
明显答案是:先打电话,后发短信了。
因为一个锁的是Class类模板,一个锁的是对象调用者。后面那个打电话不需要等待发短信,直接运行就可以了。
问题8:
如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么呢?
当然答案是:先打电话、后发短信!
因为两个对象,一样的原因:两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象的执行。
13.2.5小结new 出来的 this 是具体的一个对象
static Class 是唯一的一个模板
13.4、集合类不安全 13.4.1、List不安全我们来看一下List这个集合类:
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
List
会造成:
ArrayList 在并发情况下是不安全的!
解决方案:
切换成Vector就是线程安全的啦!
使用Collections.synchronizedList(new ArrayList<>());
public class ListTest {
public static void main(String[] args) {
List
使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();
public class ListTest {
public static void main(String[] args) {
List
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;
CopyOnWriteArrayList比Vector厉害在哪里?
Vector底层是使用synchronized关键字来实现的:效率特别低下。
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效! 13.4.2、Set不安全
和List、Set同级的还有一个BlockingQueue 阻塞队列;
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案:
使用Collections工具类的synchronized包装的Set类使用CopyonWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest {
public static void main(String[] args) {
// Set hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
Set hashSet = new CopyOnWriteArraySet<>();//解决方案2
for (int i = 1; i < 100; i++) {
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
HashSet底层是什么?
hashSet底层就是一个HashMap;
public HashSet() {
map = new HashMap<>();
}
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
13.4.3、Map不安全
回顾map的基本操作:
//map 是这样用的吗? 不是,工作中不使用这个 //默认等价什么? new HashMap<>(16,0.75); Mapmap = new HashMap<>(); //加载因子、初始化容量
默认加载因子是0.75,默认的初始容量是16
同样的HashMap基础类也存在并发修改异常!
public static void main(String[] args) {
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map map = new HashMap<>();
//加载因子、初始化容量
for (int i = 1; i < 100; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
结果同样的出现了:异常java.util.ConcurrentModificationException 并发修改异常
解决方案:
使用Collections.synchronizedMap(new HashMap<>());处理;使用ConcurrentHashMap进行并发处理
TODO:研究ConcurrentHashMap底层原理:
这里我们可以直接去研究一下,这个也是相当重要的。
可以有返回值;可以抛出异常;方法不同,run()/call()
代码测试
传统使用线程方式:
public class CallableTest {
public static void main(String[] args) {
for (int i = 1; i < 10; i++) {
new Thread(new MyThread()).start();
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
使用Callable进行多线程操作:
Calleable 泛型T就是call运行方法的返回值类型;
但是如何使用呢?
Callable怎么放入到Thread里面呢?
源码分析:
//对于Thread运行,只能传入Runnable类型的参数;
//我们这是Callable 怎么办呢?
//看JDK api文档:
//在Runnable里面有一个叫做FutureTask的实现类,我们进去看一下。
//FutureTask中可以接受Callable参数;
//这样我们就可以先把Callable 放入到FutureTask中, 如何再把FutureTask 放入到Thread就可以了。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
for (int i = 1; i < 10; i++) {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<>( Callable)).start();
MyThread thread= new MyThread();
//适配类:FutureTask
FutureTask futureTask = new FutureTask<>(thread);
//放入Thread使用
new Thread(futureTask,String.valueOf(i)).start();
//获取返回值
String s = futureTask.get();
System.out.println("返回值:"+ s);
}
}
}
class MyThread implements Callable {
@Override
public String call() throws Exception {
System.out.println("Call:"+Thread.currentThread().getName());
return "String"+Thread.currentThread().getName();
}
}
这样我们就可以使用Callable来进行多线程编程了,并且我们发现可以有返回值,并且可以抛出异常。
注意两个重点:
13.6、常用的辅助类(必会!) 13.6.1 CountDownLatch其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!
//这是一个计数器 减法
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); //每个线程都数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零 然后向下执行
System.out.println("close door");
}
}
主要方法:
countDown 减一操作;await 等待计数器归零。await等待计数器为0,就唤醒,再继续向下运行。 13.6.2 CyclickBarrier
其实就是一个加法计数器;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙~");
});
for (int i = 1; i <= 7; i++) {
//子线程
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
try {
cyclicBarrier.await(); //加法计数 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
13.6.3 Semaphore
Semaphore:信号量
抢车位:
3个车位 6辆车:
public class SemaphoreDemo {
public static void main(String[] args) {
//停车位为3个
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
int finalI = i;
new Thread(()->{
try {
semaphore.acquire(); //得到
//抢到车位
System.out.println(Thread.currentThread().getName()+" 抢到了车位{"+ finalI +"}");
TimeUnit.SECONDS.sleep(2); //停车2s
System.out.println(Thread.currentThread().getName()+" 离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
13.7、读写锁先对于不加锁的情况:
如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;
我们采用五个线程去写入,使用十个线程去读取。
我们来看一下这个的效果,如果我们不加锁的情况!
package com.ogj.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCache_ReadWriteLock{
private volatile Map map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//写入
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
}
public String get(String key){
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
String o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
return o;
}
}
所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证
package com.ogj.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCache_ReadWriteLock{
private volatile Map map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//加锁
readWriteLock.writeLock().lock();
try {
//写入
//业务流程
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); //解锁
}
}
public String get(String key){
//加锁
String o="";
readWriteLock.readLock().lock();
try {
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
以上 整个过程没有再出现错乱的情况,对于读取,我们运行多个线程同时读取,
因为这样不会造成数据不一致问题,也能在一定程度上提高效率
13.8、阻塞队列- 阻塞队列jdk1.8文档解释:
BlockingQueue
blockingQueue 是Collection的一个子类;
- 什么情况我们会使用 阻塞队列呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Z9XDqG1-1648359484099)(C:Users南风晨明AppDataRoamingTyporatypora-user-imagesimage-20210729164655223.png)]
多线程并发处理、线程池!
整个阻塞队列的家族如下:
Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;
BlockingQueue以下有link链表实现的阻塞队列、也有Array数组实现的阻塞队列
- 如何使用阻塞队列呢?
操作:添加、移除
但是实际我们要学的有:
13.8.1四组API
public static void test1(){
//需要初始化队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//抛出异常:java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//如果多移除一个
//这也会造成 java.util.NoSuchElementException 抛出异常
System.out.println(blockingQueue.remove());
}
=======================================================================================
public static void test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//弹出 如果没有元素 只会返回null 不会抛出异常
System.out.println(blockingQueue.poll());
}
=======================================================================================
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞 不会返回
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//如果队列已经满了, 再进去一个元素 这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止
// blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//如果我们再来一个 这种情况也会等待,程序会一直运行 阻塞
System.out.println(blockingQueue.take());
}
=======================================================================================
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
System.out.println("开始等待");
blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待
System.out.println("结束等待");
System.out.println("===========取值==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println("开始等待");
blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
System.out.println("结束等待");
}
13.9.SynchronousQueue同步队列
同步队列
没有容量,也可以视为容量为1的队列;
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
put方法 和 take方法;
Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;
put了一个元素,就必须从里面先take出来,否则不能再put进去值!
SynchronousQueue 的take是使用了lock锁保证线程安全的。
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue synchronousQueue = new SynchronousQueue<>();
//研究一下 如果判断这是一个同步队列
//使用两个进程
// 一个进程 放进去
// 一个进程 拿出来
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" Put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+" Put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+" Put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
// TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
// TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
13.10、线程池(重点)
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用
===> 池化技术、线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
线程池的好处:
降低资源的消耗;
提高响应的速度;
方便管理;
线程复用、可以控制最大并发数、管理线程;
线程池:三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//线程池用完必须要关闭线程池
try {
for (int i = 1; i <=100 ; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
7大参数
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
本质:三种方法都是开启的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大的线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue 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;
}
阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。
手动创建线程池
// todo
拒绝策略4种:
(1)new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
(2)new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
(3)new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
(4)new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
小结和拓展
如何去设置线程池的最大大小如何去设置?
CPU密集型和IO密集型!
CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
我们可以使用代码来来获取逻辑处理器数量。
I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
新时代的程序员**:lambda表达式、链式编程、函数式接口、Stream流式计算**
13.11.1函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//超级多的@FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用
//foreach()的参数也是一个函数式接口,消费者类的函数式接口
函数型接口可以使用lambda表达式;
代码测试:
Function函数型接口
public class Demo01 {
public static void main(String[] args) {
Function function = (str) ->{return str;};
System.out.println(function.apply("starasdas"));
}
}
Predicate断定型接口
public class Demo2 {
public static void main(String[] args) {
//判断字符串是否为空
Predicate predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test("11"));
System.out.println(predicate.test(""));
}
}
Consummer 消费型接口
public class Demo3 {
public static void main(String[] args) {
Consumer consumer = (str)->{
System.out.println(str);
};
consumer.accept("abc");
}
}
Supplier供给型接口
public class Demo4 {
public static void main(String[] args) {
Supplier supplier = ()->{return "1024";};
System.out.println(supplier.get());
}
}
13.11.2、Stream流式计算
什么是Stream流式计算?
存储+计算!
存储:集合、MySQL
计算:流式计算~
13.11.3链式编程 ===public class Test {
public static void main(String[] args) {
User user1 = new User(1,"a",21);
User user2 = new User(2,"b",22);
User user3 = new User(3,"c",23);
User user4 = new User(4,"d",24);
User user5 = new User(5,"e",25);
User user6 = new User(6,"f",26);
List list = Arrays.asList(user1, user2, user3, user4, user5, user6);
//计算交给流
//链式编程!!!!
list.stream()
.filter((u)->{ return u.getId()%2==0; })
.filter((u)->{return u.getAge()>23;})
.map((u)->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{
return uu2.compareTo(uu1);
})
.limit(1)
.forEach(System.out::println);
}
}
13.11.4,lambda表达式
外见于 多线程的学习
13.12、ForkJoin什么是ForkJoin?
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:MapReduce 核心思想->把大任务拆分为小任务!
ForkJoin 特点: 工作窃取!
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
如何使用ForkJoin?
1、通过ForkJoinPool来执行
2、计算任务 execute(ForkJoinTask> task)
3、计算类要去继承ForkJoinTask;
ForkJoin的计算类!
package com.ogj.forkjoin; import java.util.concurrent.RecursiveTask; public class ForkJoinDemo extends RecursiveTask{ private long star; private long end; //临界值 private long temp=1000000L; public ForkJoinDemo(long star, long end) { this.star = star; this.end = end; } @Override protected Long compute() { if((end-star) 测试类!
package com.ogj.forkjoin; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { test1(); test2(); test3(); } public static void test1(){ long star = System.currentTimeMillis(); long sum = 0L; for (long i = 1; i < 20_0000_0000; i++) { sum+=i; } long end = System.currentTimeMillis(); System.out.println("sum="+"时间:"+(end-star)); System.out.println(sum); } public static void test2() throws ExecutionException, InterruptedException { long star = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTasktask = new ForkJoinDemo(0L, 20_0000_0000L); ForkJoinTask submit = forkJoinPool.submit(task); Long aLong = submit.get(); System.out.println(aLong); long end = System.currentTimeMillis(); System.out.println("sum="+"时间:"+(end-star)); } public static void test3(){ long star = System.currentTimeMillis(); //Stream并行流() long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum); System.out.println(sum); long end = System.currentTimeMillis(); System.out.println("sum="+"时间:"+(end-star)); } } .parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。
reduce方法的优点:
13.13、异步回调Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 --> 发送ajax异步请求给后端
但是我们平时都使用CompletableFuture
(1)没有返回值的runAsync异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException { // 发起 一个 请求 System.out.println(System.currentTimeMillis()); System.out.println("---------------------"); CompletableFuturefuture = CompletableFuture.runAsync(()->{ //发起一个异步任务 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"....."); }); System.out.println(System.currentTimeMillis()); System.out.println("------------------------------"); //输出执行结果 System.out.println(future.get()); //获取执行结果 } (2)有返回值的异步回调supplyAsync
//有返回值的异步回调 CompletableFuturecompletableFuture=CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(2); int i=1/0; } catch (InterruptedException e) { e.printStackTrace(); } return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { //success 回调 System.out.println("t=>" + t); //正常的返回结果 System.out.println("u=>" + u); //抛出异常的 错误信息 }).exceptionally((e) -> { //error回调 System.out.println(e.getMessage()); return 404; }).get()); whenComplete: 有两个参数,一个是t 一个是u
T:是代表的 正常返回的结果;
U:是代表的 抛出异常的错误信息;
如果发生了异常,get可以获取到exceptionally返回的值;
13.14.JMM什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
线程解锁前,必须把共享变量立刻刷回主存;
线程加锁前,必须读取主存中的最新值到工作内存中;
加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
8种操作:
Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
JMM对这8种操作给了相应的规定:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存不允许一个线程将没有assign的数据从工作内存同步回主内存一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量对一个变量进行unlock操作之前,必须把此变量同步回主内存
遇到问题:程序不知道主存中的值已经被修改过了!;
13.15、VolatileVolatile 是 Java 虚拟机提供 轻量级的同步机制
保证可见性不保证原子性禁止指令重排 13.15.1.保证可见性
public class JMMDemo01 { // 如果不加volatile 程序会死循环 // 加了volatile是可以保证可见性的 private volatile static Integer number = 0; public static void main(String[] args) { //main线程 //子线程1 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //子线程2 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number=1; System.out.println(number); } }13.15.2.不保证原子性原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
public class VDemo02 { private static volatile int number = 0; public static void add(){ number++; //++ 不是一个原子性操作,是两个~3个操作 // } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+number); } }如果不加lock和synchronized ,怎么样保证原子性?
解决方法:使用JUC下的原子包下的class;
代码如下: public class VDemo02 { private static volatile AtomicInteger number = new AtomicInteger(); public static void add(){ // number++; number.incrementAndGet(); //底层是CAS保证的原子性 } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+number); } }
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
原子类为什么这么高级?
13.15.3、禁止指令重排什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1 int y=2; //2 x=x+5; //3 y=x*x; //4 //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324 //可不可能是 4123? 不可能的可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A 线程B
x=a y=b
b=1 a=2
正常的结果: x = 0; y =0;线程A 线程B
x=a y=b
b=1 a=2
可能在线程A中会出现,先执行b=1,然后再执行x=a;在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.
volatile可以避免指令重排:
内存屏障:CPU指令,作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMXkYvsA-1648359484102)(C:Users南风晨明AppDataRoamingTyporatypora-user-imagesimage-20210729171848624.png)]
总结
volatile可以保证可见性;不能保证原子性由于内存屏障,可以保证避免指令重排的现象产生
**面试官:那么你知道在哪里用这个内存屏障用得最多呢?**单例模式
13.16、玩转单例模式 13.16.1.饿汉式、DCL懒汉式饿汉式
public class Hungry { private byte[] data1=new byte[1024*1024]; private byte[] data2=new byte[1024*1024]; private byte[] data3=new byte[1024*1024]; private byte[] data4=new byte[1024*1024]; private Hungry(){ } private final static Hungry hungry = new Hungry(); public static Hungry getInstance(){ return hungry; } }
DCL懒汉式
//懒汉式单例模式 public class LazyMan { private static boolean key = false; private LazyMan(){ synchronized (LazyMan.class){ if (key==false){ key=true; } else{ throw new RuntimeException("不要试图使用反射破坏异常"); } } System.out.println(Thread.currentThread().getName()+" ok"); } private volatile static LazyMan lazyMan; //双重检测锁模式 简称DCL懒汉式 public static LazyMan getInstance(){ //需要加锁 if(lazyMan==null){ synchronized (LazyMan.class){ if(lazyMan==null){ lazyMan=new LazyMan(); } } } return lazyMan; } //单线程下 是ok的 //但是如果是并发的 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { //Java中有反射 // LazyMan instance = LazyMan.getInstance(); Field key = LazyMan.class.getDeclaredField("key"); key.setAccessible(true); ConstructordeclaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); //无视了私有的构造器 LazyMan lazyMan1 = declaredConstructor.newInstance(); key.set(lazyMan1,false); LazyMan instance = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(lazyMan1); System.out.println(instance == lazyMan1); } } 静态内部类 //静态内部类 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.holder; } public static class InnerClass{ private static final Holder holder = new Holder(); } } 单例不安全, 因为反射
13.16.2枚举枚举当中没有无参构造,只有两个参数的有参构造
//enum 是什么? enum本身就是一个Class 类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; ConstructordeclaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle. () EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } 使用枚举,我们就可以防止反射破坏了。
枚举类型使用JAD最终反编译后源码:
如果我们看idea 的文件:会发现idea骗了我们,居然告诉我们是有有参构造的,我们使用jad进行反编译。
public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }13.17、深入理解CAS什么是CAS?
大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构
public class casDemo { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }总结:
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
循环会耗时;一次性只能保证一个共享变量的原子性;它会存在ABA问题
CAS:ABA问题?(狸猫换太子)
线程1:期望值是1,要变成2; 线程2:两个操作: 1、期望值是1,变成3 2、期望是3,变成1 所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1; public class casDemo { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 // atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }13.18、原子引用解决ABA问题,对应的思想:就是使用了乐观锁~
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!
那么如果我们使用小于128的时候:
正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。
13.19、各种锁的理解 13.19.1、公平锁、非公平锁公平锁:非常公平;不能插队的,必须先来后到;
public ReentrantLock() { sync = new NonfairSync(); }非公平锁:非常不公平,允许插队的,可以改变顺序。
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }13.19.2、可重入锁可重入锁(递归锁)
Synchronized锁 public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName()+"=> sms"); call();//这里也有一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"=> call"); } }13.19.3.lock锁//lock public class Demo02 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone2{ Lock lock=new ReentrantLock(); public void sms(){ lock.lock(); //细节:这个是两把锁,两个钥匙 //lock锁必须配对,否则就会死锁在里面 try { System.out.println(Thread.currentThread().getName()+"=> sms"); call();//这里也有一把锁 } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> call"); }catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } }lock锁必须配对,相当于lock和 unlock 必须数量相同;在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁; 13.19.4、自旋锁
spinlock public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }13.19.5.自我设计自旋锁:public class SpinlockDemo { //int 0 //thread null AtomicReferenceatomicReference=new AtomicReference<>(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"===> mylock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ System.out.println(Thread.currentThread().getName()+" ==> 自旋中~"); } } //解锁 public void myunlock(){ Thread thread=Thread.currentThread(); System.out.println(thread.getName()+"===> myUnlock"); atomicReference.compareAndSet(thread,null); } } public class TestSpinLock { public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.unlock(); //使用CAS实现自旋锁 SpinlockDemo spinlockDemo=new SpinlockDemo(); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myunlock(); } },"t1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myunlock(); } },"t2").start(); } } 运行结果:
t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。
13.19.6死锁死锁是什么?
死锁测试,怎么排除死锁?:
package com.ogj.lock; import java.util.concurrent.TimeUnit; public class DeadLock { public static void main(String[] args) { String lockA= "lockA"; String lockB= "lockB"; new Thread(new MyThread(lockA,lockB),"t1").start(); new Thread(new MyThread(lockB,lockA),"t2").start(); } } class MyThread implements Runnable{ private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA); } } } }解决问题:
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack 进程进程号 找到死锁信息
》》》完结撒花初识Java入门基础(上)
初识Java入门基础(中)初识Java入门基础(下)



