三天回顾Java——零基础速成实战
- 第二天 初识Java入门基础(下)
- 11.网络编程
- 11.1、概述
- 11.2、**网络通信的要素**
- 11.3、IP
- 11.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、反射相关的主要API
- 12.6.3、反射初体验
- 12.7.1:得到Class类的几种方式
- 13、JUC详细学习
- 13.1、什么是JUC
- 13.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 CountDownLatch
- 13.6.2 CyclickBarrier
- 13.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、ForkJoin
- 13.13、异步回调
- 13.14.JMM
- 13.15、Volatile
- 13.15.1.保证可见性
- 13.15.2.**不保证原子性**
- **13.15.3、禁止指令重排**
- 13.16、玩转单例模式
- 13.16.1.饿汉式、DCL懒汉式
- 13.16.2枚举
- 13.17、深入理解CAS
- 13.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.getHostAddressIPInetAddress.getHostName域名或自己电脑的名字InetSocketAddress实现 IP 地址及端口InetAddress实现 IP 地址ServerSocket建立服务的端口.accept阻塞监听等待连接Socket创建连接.getInputStream获取IO输入流.getOutputStream获取IO输出流ByteArrayOutputStreambyte类型数组管道输出流FileOutputStream文件字符输出流FileInputStream文件字符输入流shutdownOutput停止输出DatagramSocket数据包端口DatagramPacket数据包.send发送.receive阻塞接收BufferedReader缓存区读取InputStreamReader输入流读取.readLine读取的一行内容URL统一资源定位符.openConnection打开连接HttpURLConnection指定协议HTTP
11.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.getHostAddressIPInetAddress.getHostName域名或自己电脑的名字
11.4、端口
端口表示计算机上的一个程序的进程;
不同的进程有不同的端口!用来区分软件!
被规定0~65535,不能使用相同的端口
TCP,UDP:65535*2,tcp : 80, udp : 80 这样不影响。单个协议下,端口号不能冲突
端口分类
- 公有端口 0~1023 (尽量不用)
- HTTP:80
- HTTPS:443
- FTP:21
- Telent:23
- 程序注册端口:1024~49151,分配用户或者程序
- Tomcat:8080
- MySQL:3306
- Oracle:1521
- 动态、私有:49152~65535(尽量不用)
- 公有端口 0~1023 (尽量不用)
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——我要连接 ——>BB—— 你可以连接 ——>AA—— 那我连接了 ——>B连接成功!四次挥手A——我要断开——>BB——你可以断开——>AB——你确定断开?——>AA——我确定断开!——>B连接断开
- 客户端、服务端:主动和被动的过程
- 传输完成,释放连接,效率低
- UDP:发短信
- 不连接、不稳定
- 客户端、服务端:没有明确的界限
- 不管有没有准备好,都可以发给你
- DDOS:洪水攻击!(饱和攻击)
11.6、TCP
服务器
- 建立服务的端口 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
服务端
- 自定义 S
- Tomcat 服务器 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();}}@Overridepublic void run() {try {while(true){//准备数据:控制台读取 System.inBufferedReader 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();}}@Overridepublic 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");//协议httpSystem.out.println(url.getProtocol());//主机ip,localhostSystem.out.println(url.getHost());//端口,8080System.out.println(url.getPort());//文件,/helloworld/index.jspSystem.out.println(url.getPath());//全路径,/helloworld/index.jsp?username=kuangshen&password==123System.out.println(url.getFile());//参数,username=kuangshen&password==123System.out.println(url.getQuery());}}
URL网络下载
public class UrlDown {public static void main(String[] args) throws Exception {//1.下载地址URL url = new URL("https://**1.**2.com/**/**/***.jpg");//2.连接到这个资源 HTTPHttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();//3.输入流InputStream inputStream = urlConnection.getInputStream();//4.下载到存放地址FileOutputStream fos = new FileOutputStream("123.jpg");//5.写出数据byte[] buffer = new byte[1024];int len ;while((len = inputStream.read(buffer))!=-1){fos.write(buffer,0,len);}fos.close();inputStream.close();urlConnection.disconnect(); //断开连接}}
URL统一资源定位符.openConnection打开连接HttpURLConnection指定协议HTTP
12、注解和反射
12.1、注解概念
Annotation是从JDK5.0开始引入的新技术
Annotation的作用:
可以对程序作出解释,这一点和注释comment类似
对程序进行检查和约束,例如@Override
可以被其他程序(比如:编译器等)读取
Annotation的格式:
注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarning(value=”unchecked”)
Annotation在哪里使用?
可以附加在package、class、method、field等上面,相等于给他们添加了额外的辅助信息,然后结合反射机制实现对这些元数据的访问
12.2:内置注解
12.2.1、内置注解
@Override:定义在java.lang包中,此注解只适用于修饰方法,表示该方法打算重写父类中的同名方法,并且具有检查作用
@Deprecated的程序元素是程序员不鼓励使用的程序元素,通常是因为它是危险的,或者因为它已经过时了,然后存在更好的替代方法,但是你使用也没有任何影响,
@SupressWarnings:
@SuppressWarnings(“all”) // 压制所有警告,下面有例子:
@SuppressWarnings(“unchecked”) // 压制”unchecked”警告
@SuppressWarnings(value={“unchecked”, “deprecation”}) // 压制”unchecked”, “deprecation”警告
注意:
public @interface SuppressWarnings {
String[] value();
},
其中value是参数名,而String[]是参数是类型,当注解只使用value这一个参数的时候,value可以省略,例如上面的@SuppressWarnings(“all”)和@SuppressWarnings(“unchecked”)就是省略了前面的value,但是仅限参数名是value,并且注解中只使用value这一个参数的情况才可以 省略value
12.3:什么是元注解
12.3.1、元注解
元注解作用:负责注解其他注解,Java中定义了4个标准的元注解()类型,他们被用来对其他注解类型进行解释说明限制
元注解位置:在java.lang.annotation包中
元注解四大类型
@Target:
用于描述注解的使用范围,也就是描述注解可以使用在什么地方,public @interface Target {ElementType[] value();}可以看出可以它是一个数组,可以有多个值,里面的值设置的都是定义的注解MyAnnotation的使用范围,比如在类上、方法上等等,你可以通过@Target,然后点击里面的ElementType去查看
@Retention:
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME),SOURCE是源码级别,CLASS 是源码级别和编译之后的字节码文件中有效,RUNTIME是在源码级别、编译之后的字节码文件、JVM中运行都有效,最常用的就是RUNTIME
@Documented:
说明该注解将被包含在javadoc文档中
@Inherited:
说明子类可以继承父类中的该注解
12.4:自定义注解
12.4.1、自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
@interface 用来声明一个注解,格式是:修饰符 @interface 注解名{定义内容}
12.5:反射概述
12.5.1、反射概念
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并且能直接操作任意对象的内部属性以及方法
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子可以看到类的结构,这就是反射
12.6:获取反射对象
12.6.1、反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
……
12.6.2、反射相关的主要API
java.lang.Class:代表一个类,它就是Object类getClass()方法的返回值,它是唯一的所以类都指向它
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
……
12.6.3、反射初体验
public static void main(String[] args) throws ClassNotFoundException {// 通过反射返回类的Class对象,这里的Class就是Object类中getClass()方法的返回值Class是一样的,获取的c1是Class类的对象Class c1 =Class.forName("com.atguigu.demo.User");System.out.println(c1);// 一个类在方法区中只有一个Class对象// 一个类被加载之后,类的整个结构(构造器、方法、属性等等)都会被封装在Class对象中Class c2 =Class.forName("com.atguigu.demo.User");System.out.println(c1.hashCode());System.out.println(c2.hashCode());}}@Dataclass User{private Integer id;private String name;private Integer age;}结果:class com.atguigu.demo.User10665162071066516207
####12.7、Class类详解
12.7.1:得到Class类的几种方式
Class类:
- Class本身也是一个类,不过这个类只有一个,所有的类都执行它,例如User类、Person类等
- Class对象是Class类的对象,一个加载的类(例如User.java)在JVM中只会有一个Class对象
- 一个Class对象对应的是一个加载到JVM中的对应的XXX.class文件
- 每个类(例如User.java)的实例(例如User u = new User()中的u)都会记着自己是由哪个Class对象所生成
- 通过CLass对象可以完整地得到一个类(例如User.java)中所有被加载的信息
Class类(只有一个,和Object类中getClass()方法中返回的Class是同一个)是Reflection的根源,针对任何你想动态加载、运行的类、唯有先获得类(例如User.java)对应的Class对象
以上说了三个部分:Class类、Class对象、类、类的实例,通过类可以创建类的实例,通过Class类可以获取某类的Class对象,通过Class对象可以获取类中的所有信息(方法、属性等等),当然也可以创建类的实例,另外Class对象是类具有的,类可以做的它也能做
Class类的常用方法
获取Class类的实例
通过类的class属性获取,该方法最为安全可靠,程序性能最高Class c1 = User.class;1通过类的实例中的getClass()方法获取Class c2 = user.getClass();1通过类的全限定类名获取Class c3 = Class.forName("com.atguigu.demo.User");14.通过基本内置类型的包装类的Type属性(了解)Class c4 = Integer.TYPE;1通过ClassLoader类加载器获取Constructor constructor = c1.getDeclaredConstructor(String.class);Class c5 = constructor.getDeclaringClass();System.out.println(c5.hashCode());
13、JUC详细学习
13.1、什么是JUC
寻找来源:源码+官方文档
面试高频问JUC~!
JUC是 java.util.concurrent
java.util 是Java的一个工具包~
业务:普通的线程代码 Thread
Runnable: 没有返回值、效率相比于Callable 相对较低!
13.2、线程和进程
进程:一个程序,QQ.EXE Music.EXE;数据+代码+pcb
一个进程可以包含多个线程?
- 至少包含一个main线程!
Java默认有几个线程?
- 2个线程!即为 main线程、GC线程
线程:开了一个进程Typora,写字,等待几分钟会进行自动保存(线程负责的)
对于Java而言开启线程的三种方式:Thread、Runable、Callable
提问?JAVA真的可以开启线程吗? 开不了的!
public synchronized void start() {/** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}**这是一个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的资源!
13.2.2线程有几个状态?
线程的状态:6个状态
public enum State {/** * Thread state for a thread which has not yet started. *///运行NEW, /** * Thread state for a runnable thread.A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. *///运行RUNNABLE,/** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. *///阻塞BLOCKED,/** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * * - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
* * A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. *///等待WAITING,/** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*///超时等待TIMED_WAITING,/** * Thread state for a terminated thread. * The thread has completed execution. *///终止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();}}//资源类//属性+方法//oopclass 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;//+1public synchronized void increment() throws InterruptedException {if(number!=0){//等待操作this.wait();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知其他线程 我+1完毕了this.notifyAll();}//-1public 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();//+1public 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();}}//-1public 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来指定通知进程~
/** * A 执行完 调用B * B 执行完 调用C * C 执行完 调用A */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 3Cpublic 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");//唤醒3number=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");//唤醒1number=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<Object> arrayList = new ArrayList<>();for(int i=1;i<=10;i++){new Thread(()->{arrayList.add(UUID.randomUUID().toString().substring(0,5));System.out.println(arrayList);},String.valueOf(i)).start();}}}
会造成:
ArrayList 在并发情况下是不安全的!
解决方案:
切换成Vector就是线程安全的啦!
使用Collections.synchronizedList(new ArrayList());
public class ListTest {public static void main(String[] args) {List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());for(int i=1;i<=10;i++){new Thread(()->{arrayList.add(UUID.randomUUID().toString().substring(0,5));System.out.println(arrayList);},String.valueOf(i)).start();}}}
- 使用JUC中的包:List arrayList = new CopyOnWriteArrayList();
public class ListTest {public static void main(String[] args) {List<Object> arrayList = new CopyOnWriteArrayList<>();for(int i=1;i<=10;i++){new Thread(()->{arrayList.add(UUID.randomUUID().toString().substring(0,5));System.out.println(arrayList);},String.valueOf(i)).start();}}}
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()); //解决方案1Set<String> hashSet = new CopyOnWriteArraySet<>();//解决方案2for (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);Map<String, String> map = new HashMap<>();//加载因子、初始化容量
默认加载因子是0.75,默认的初始容量是16
同样的HashMap基础类也存在并发修改异常!
public static void main(String[] args) {//map 是这样用的吗?不是,工作中不使用这个//默认等价什么? new HashMap(16,0.75);Map<String, String> 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底层原理:
这里我们可以直接去研究一下,这个也是相当重要的。
13.5、Callable(简单)
- 可以有返回值;
- 可以抛出异常;
- 方法不同,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{@Overridepublic 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();//适配类:FutureTaskFutureTask<String> futureTask = new FutureTask<>(thread);//放入Thread使用new Thread(futureTask,String.valueOf(i)).start();//获取返回值String s = futureTask.get();System.out.println("返回值:"+ s);}}}class MyThread implements Callable<String> {@Overridepublic 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 {//总数是6CountDownLatch 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); //停车2sSystem.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<String,String> 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<String,String> 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\南风晨明\AppData\Roaming\Typora\typora-user-images\image-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<String> 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<Runnable>()));}public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
本质:三种方法都是开启的ThreadPoolExecutor
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;}
阿里巴巴的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数的一倍到两倍之间。
13.11、四大函数式接口(必需掌握)
新时代的程序员**:lambda表达式、链式编程、函数式接口、Stream流式计算**
13.11.1函数式接口:
只有一个方法的接口
@FunctionalInterfacepublic interface Runnable {public abstract void run();}//超级多的@FunctionalInterface//简化编程模型,在新版本的框架底层大量应用//foreach()的参数也是一个函数式接口,消费者类的函数式接口
- 函数型接口可以使用lambda表达式;
代码测试:Function函数型接口/** * Function函数型接口 */ public class Demo01 { public static void main(String[] args) { Function<String,String> function = (str) ->{return str;}; System.out.println(function.apply("starasdas")); } } Predicate断定型接口/** * 断定型接口:有一个输入参数,返回值只能是 布尔值! */ public class Demo2 { public static void main(String[] args) { //判断字符串是否为空 Predicate<String> 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<String> consumer = (str)->{ System.out.println(str); }; consumer.accept("abc"); } } Supplier供给型接口/** * 供给型接口,只返回,不输入 */ public class Demo4 { public static void main(String[] args) { Supplier<String> 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<User> 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<Long> {private long star;private long end;//临界值private long temp=1000000L;public ForkJoinDemo(long star, long end) {this.star = star;this.end = end;}/** * 计算方法 * @return Long */@Overrideprotected Long compute() {if((end-star)<temp){Long sum = 0L;for (Long i = star; i < end; i++) {sum+=i;}//System.out.println(sum);return sum;}else {//使用forkJoin 分而治之 计算//计算平均值long middle = (star+ end)/2;ForkJoinDemo forkJoinDemoTask1 = new ForkJoinDemo(star, middle);forkJoinDemoTask1.fork();//拆分任务,把线程任务压入线程队列ForkJoinDemo forkJoinDemoTask2 = new ForkJoinDemo(middle, end);forkJoinDemoTask2.fork();//拆分任务,把线程任务压入线程队列long taskSum = forkJoinDemoTask1.join() + forkJoinDemoTask2.join();return taskSum;}}}
测试类!
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);}/** * 使用ForkJoin */public static void test2() throws ExecutionException, InterruptedException {long star = System.currentTimeMillis();ForkJoinPool forkJoinPool = new ForkJoinPool();ForkJoinTask<Long> task = new ForkJoinDemo(0L, 20_0000_0000L);ForkJoinTask<Long> submit = forkJoinPool.submit(task);Long aLong = submit.get();System.out.println(aLong);long end = System.currentTimeMillis();System.out.println("sum="+"时间:"+(end-star));}/** * 使用Stream 并行流 */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("---------------------");CompletableFuture<Void> future = 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
//有返回值的异步回调CompletableFuture<Integer> completableFuture=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、Volatile
Volatile 是 Java 虚拟机提供 轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
13.15.1.保证可见性
public class JMMDemo01 {// 如果不加volatile 程序会死循环// 加了volatile是可以保证可见性的private volatile static Integer number = 0;public static void main(String[] args) {//main线程//子线程1new Thread(()->{while (number==0){}}).start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}//子线程2new 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在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
/** * 不保证原子性 * number <=2w * */ 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){ //maingc 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=== 20000for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j = 1; j <= 1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//maingcThread.yield();}System.out.println(Thread.currentThread().getName()+",num="+number);}}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
原子类为什么这么高级?
13.15.3、禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
- 源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1int y=2; //2x=x+5; //3y=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\南风晨明\AppData\Roaming\Typora\typora-user-images\image-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();/** * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * *就有可能出现指令重排问题 *比如执行的顺序是1 3 2 等 *我们就可以添加volatile保证指令重排问题 */}}}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);Constructor<LazyMan> declaredConstructor = 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;Constructor<EnumSingle> declaredConstructor = 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,变成32、期望是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、公平锁、非公平锁
- 公平锁:非常公平;不能插队的,必须先来后到;
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
- 非公平锁:非常不公平,允许插队的,可以改变顺序。
/** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ 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锁
//lockpublic 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、自旋锁
spinlockpublic 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 nullAtomicReference<Thread> atomicReference=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;}@Overridepublic 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入门基础(下)