day23
一、Lock锁
通过之前的学习,我们可以理解和使用同步代码块及同步方法的锁去解决问题。但是这个锁是什么时候加的,又是什么时候释放的,我们是不明确的。所以官方在JDK1.5 之后提供了一个新的接口Lock,它除了具有之前锁功能外,还很明确的看到在哪里加上了锁以及在哪里释放了及其它额外的功能。
- 锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。
- Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。(总结:Lock除了具有synchronized的基本功能外,还有额外的功能)
- 因为Lock是接口,所以必须要通过Lock的实现类去创建对象。
1.1 ReentrantLock
是Lock接口的一个实现类。具有与使用
synchronized
方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大
创建锁对象
ReentrantLock rl = new ReentrantLock();
添加和释放锁
void lock() 获取锁
void unlock() 试图释放此锁
package com.ujiuye.demo02_Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketsRunnable implements Runnable {
static int tickets = 100;
//创建Lock对象
ReentrantLock rl = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//使用之前,加锁
rl.lock();
//证明有票可卖
if(tickets > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了1张票,还剩" + --tickets + "张票");
}else{
break;
}
} finally {
//使用之后,释放锁
rl.unlock();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
SellTicketsRunnable str = new SellTicketsRunnable();
Thread t = new Thread(str, "淘票票");
Thread t2 = new Thread(str, "美团");
Thread t3 = new Thread(str, "猫眼电影");
t.start();
t2.start();
t3.start();
}
}
二、线程的生命周期
线程是一个动态的概念,线程有创建的时候,也有运行和变化的时候,当然也有销毁的时候,这一系列变化被我们称为线程的生命周期。
2.1 理论状态
创建态:使用new新建一个线程 Thead t = new Thread();
就绪态:通过start方法启动了线程
执行态:抢到了CPU执行权
阻塞态:在执行的过程中,遇到了某些突发问题而停止了执行,转而进入的状态
结束态:线程结束了
以上几种生命周期是我们的理论状态,官方提供了对线程生命周期更精准的描述。
2.2 官方描述的状态 Thread.State
线程状态。线程可以处于下列状态之一
NEW
至今尚未启动的线程处于这种状态。 Thread t = new Thread();(相当于新建态)
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。 但它可能正在等待操作系统中的其他资源,比如处理器。 (相当于就绪和运行态)
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。 (相当于阻塞态)
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
三、死锁
详情参考附带笔记
四、线程池
线程池就是装很多线程的池子。(就像水池可以装很多水一样)
4.1 为什么使用线程池
- 因为创建一个线程需要向操作系统申请执行线程的资源。但是如果要是很多小任务需要执行的话,那么就需要频繁的向操作系统申请资源,这样效率很低。
- 如果有一个池子可以装指定数量的已经创建好的线程。等到执行任务的时候,从池子中去取一个线程用,用完了归还到池子中,下一个线程使用的时候再取出来,循环往复。这样可以极大的提高效率。
4.2 如何使用
第一步:获取线程池对象
使用Executors类调用它的静态方法获取到线程池对象ExecutorService。
static ExecutorService newFixedThreadPool(int nThreads) 创建指定个数的线程
第二步:创建任务类对象
方式一:Runnable 【常用】
方式二:Callable
第三步:通过线程池对象调用submit方法将任务提交给线程池
submit(Runnable task)
submit(Callable task)
第四步:关闭线程池
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务(不接受新任务,也就可以理解为是关闭了线程池)
五、单例设计模式
5.1 设计模式
模式:就是解决问题的一种套路。
设计模式:就是在设计类、接口、方法,或者用来实现某种架构的套路。
注意:设计模式不是一种方法或者结束,也不归属于某一种语言,而只是解决问题的一种思想。当然编程语言中(比如Java)都会设计到设计模式
5.2 单例设计模式
5.2.1 概念
单例:就是单个对象。(之前的对象,我们也可以叫做实例)
单例设计模式要保证的就是内存中只有一个对象。
5.2.2 为什么使用单例设计模式
因为有些对象我们需要多次使用。那么就需要频繁的创建,这样效率很低下。所以我们可以先在类中将对象给创建好,并且提供给类的使用者。
注意:一个类只需要一个对象
5.2.3 如何设计
- 成员变量私有化
- 构造方法私有化
- 给外界提供一个公共的静态的方法,此方法可以获取到唯一的一个对象
5.2.4 实现方式
饿汉式
就是外界没有调用获取对象的方法,对象就已经被提前创建好了
/*
单例设计模式-饿汉式
*/
public class SingleDemo {
//1. 成员变量私有化
private static SingleDemo sd = new SingleDemo();
//2. 构造方法私有化
private SingleDemo(){
}
//3. 给外界提供一个静态的方法,此方法可以获取到唯一的一个对象。
public static SingleDemo getInstance() {
return sd;
}
}
public class Demo {
public static void main(String[] args) {
SingleDemo sd = SingleDemo.getInstance();
System.out.println(sd); //com.ujiuye.demo05_单例设计模式.SingleDemo@1b6d3586
SingleDemo sd2 = SingleDemo.getInstance();
System.out.println(sd2); //com.ujiuye.demo05_单例设计模式.SingleDemo@1b6d3586
}
}
懒汉式
等到外界调用获取对象方法的时候,才去创建对象
/*
单例设计模式-懒汉式
*/
public class SingleDemo {
//1. 成员变量私有化
private static SingleDemo sd = null;
//2. 构造方法私有化
private SingleDemo(){
}
//3. 给外界提供一个公共的静态的方法,此方法可以获取到唯一的一个对象
public static SingleDemo getInstance(){
//先判断SingleDemo是否为空
if(sd == null){
//如果为空,那么就创建一个对象
sd = new SingleDemo();
}
//如果不为空,那么就直接返回对象即可
return sd;
}
}
public class Demo {
public static void main(String[] args) {
SingleDemo sd = SingleDemo.getInstance();
System.out.println(sd); //com.ujiuye.demo05_单例设计模式.懒汉式.SingleDemo@1b6d3586
SingleDemo sd2 = SingleDemo.getInstance();
System.out.println(sd2); //com.ujiuye.demo05_单例设计模式.懒汉式.SingleDemo@1b6d3586
}
}
安全的单例设计模式 【面试常问,掌握】
懒汉式可能会遇到的问题:线程不安全。如下图所示
public class SingleDemo {
//1. 成员变量私有化
private static SingleDemo sd = null;
//2. 构造方法私有化
private SingleDemo(){
}
//3. 给外界提供一个公共的静态的方法,此方法可以获取到唯一的一个对象
//方式一:使用同步方法
// public static synchronized SingleDemo getInstance(){
//
// //先判断SingleDemo是否为空
// if(sd == null){
// //如果为空,那么就创建一个对象
// sd = new SingleDemo();
// }
//
// //如果不为空,那么就直接返回对象即可
// return sd;
// }
//方式二:使用同步代码块
public static SingleDemo getInstance(){
//先判断SingleDemo是否为空,如果不为空,那么直接返回SingleDemo对象
if(sd == null){
//如果为空,那么很有可能发生线程安全问题,所以需要用锁优化
synchronized (SingleDemo.class){
//先判断SingleDemo是否为空
if(sd == null){
//如果为空,那么就创建一个对象
sd = new SingleDemo();
}
}
}
//如果不为空,那么就直接返回对象即可
return sd;
}
}
public class Demo {
public static void main(String[] args) {
SingleDemo sd = SingleDemo.getInstance();
System.out.println(sd); //com.ujiuye.demo05_单例设计模式.安全的单例设计模式.SingleDemo@1b6d3586
SingleDemo sd2 = SingleDemo.getInstance();
System.out.println(sd2); //com.ujiuye.demo05_单例设计模式.安全的单例设计模式.SingleDemo@1b6d3586
}
}
老汉式 【了解】
相当于是饿汉式的一种。不常见,了解即可
public class SingleDemo {
//使用final修饰就不能在外界修改了,保证了唯一性
public static final SingleDemo sd = new SingleDemo();
private SingleDemo(){
}
}
public class Demo {
public static void main(String[] args) {
SingleDemo sd = SingleDemo.sd;
System.out.println(sd); //com.ujiuye.demo05_单例设计模式.老汉式.SingleDemo@1b6d3586
SingleDemo sd2 = SingleDemo.sd;
System.out.println(sd2); //com.ujiuye.demo05_单例设计模式.老汉式.SingleDemo@1b6d3586
}
}
六、枚举
枚举:就是一一列举,就是将常量的值给意义列举出来。但是这些列举的值只能是枚举类中声明好的值。比如之前我们学习的线程的状态Thread.State中只有六种状态。
6.1 作用
比如一周七天,一年12个月,4个方向这是数据的个数都是固定的,那么我们就可以使用枚举将它们给全部声明好。
枚举可以类比单例来理解。单例是一个类中只能有一个对象,而枚举就是一个类中可以有多个对象,只不过这多个对象是有限个的。这样才能构成枚举。
6.2 手动实现枚举 (了解)
注意:
- 枚举就是多个对象
- 手动实现的时候一定要将构造方法私有(因为枚举的对象个数是有限的,不能让外界随意创建)
需求: 将方向 上下左右 定义到枚举中
方式一
/*
枚举第一种手动实现方式:
方向:上下左右 (也就是上下左右四个对象)
*/
public class DirectionDemo {
public static final DirectionDemo UP = new DirectionDemo();
public static final DirectionDemo DOWN = new DirectionDemo();
public static final DirectionDemo LEFT = new DirectionDemo();
public static final DirectionDemo RIGHT = new DirectionDemo();
//构造方法私有化
private DirectionDemo(){
}
}
public class Demo {
public static void main(String[] args) {
DirectionDemo up = DirectionDemo.UP;
System.out.println(up); //com.ujiuye.demo06_Enum.手动.方式一.DirectionDemo@1b6d3586
DirectionDemo down = DirectionDemo.DOWN;
System.out.println(down); //com.ujiuye.demo06_Enum.手动.方式一.DirectionDemo@4554617c
}
}
方式二
public class DirectionDemo {
public static final DirectionDemo UP = new DirectionDemo("上");
public static final DirectionDemo DOWN = new DirectionDemo("下");
public static final DirectionDemo LEFT = new DirectionDemo("左");
public static final DirectionDemo RIGHT = new DirectionDemo("右");
private String name;
private DirectionDemo(String name){
this.name = name;
}
//获取变量值
public String getName(){
return name;
}
}
public class Demo {
public static void main(String[] args) {
DirectionDemo left = DirectionDemo.LEFT;
System.out.println(left); //com.ujiuye.demo06_Enum.手动.方式二.DirectionDemo@1b6d3586
System.out.println(left.getName()); //左
DirectionDemo right = DirectionDemo.RIGHT;
System.out.println(right); //com.ujiuye.demo06_Enum.手动.方式二.DirectionDemo@4554617c
System.out.println(right.getName()); //右
}
}
方式三
/*
匿名内部类:
new 类名或接口名(){
方法的重写;
}
*/
public abstract class DirectionDemo {
public static final DirectionDemo UP = new DirectionDemo("上"){
@Override
public String show() {
return "上";
}
};
public static final DirectionDemo DOWN = new DirectionDemo("下"){
@Override
public String show() {
return "下";
}
};
public static final DirectionDemo LEFT = new DirectionDemo("左"){
@Override
public String show() {
return "左";
}
};
public static final DirectionDemo RIGHT = new DirectionDemo("右"){
@Override
public String show() {
return "右";
}
};
private String name;
private DirectionDemo(String name){
this.name = name;
}
//获取变量值
public String getName(){
return name;
}
//定义一个抽象方法来获取变量值
public abstract String show();
}
public class Demo {
public static void main(String[] args) {
DirectionDemo up = DirectionDemo.UP;
System.out.println(up); //com.ujiuye.demo06_Enum.手动.方式三.DirectionDemo$1@1b6d3586
System.out.println(up.getName()); //上
System.out.println(up.show()); //上
DirectionDemo right = DirectionDemo.RIGHT;
System.out.println(right); //com.ujiuye.demo06_Enum.手动.方式三.DirectionDemo$4@4554617c
System.out.println(right.getName());//右
System.out.println(right.show()); //右
}
}
6.2 官方提供的枚举
JDK1.5出现的内容。
6.2.1 格式
权限修饰符 enum 枚举名 {
枚举项1, 枚举项2, 枚举项3, ...;
}
6.2.2 注意事项
- 定义枚举类的时候一定要使用enum关键字
- 枚举都继承子Enum类
- 枚举中的第一行必须是枚举项,多个枚举项中间有英文状态的逗号分隔。
如果最后一个枚举项后面没有任何内容了,那么可以省略分号
如果最后一个枚举项后面有内容,那么分号不可省略。(建议永远不要省略) - 枚举中有构造方法,但是此构造方法必须要私有(枚举中的构造方法默认就是私有的)
如何使用构造方法?
枚举项(参数); - 枚举中可以有抽象方法,此抽象方法必须要在枚举项中实现
- 枚举可以用在switch语句中
6.2.3 常见方法
int ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
int compareTo(E o) 较此枚举与指定对象的顺序
String name() 返回此枚举常量的名称,在其枚举声明中对其进行声明
String toString() 返回枚举常量的名称,它包含在声明中
values() 获取存储所有枚举项的数组
public enum DirectionDemo {
UP("上"){
@Override
public String show() {
return "上";
}
},
DOWN("下"){
@Override
public String show() {
return "下";
}
},
LEFT("左"){
@Override
public String show() {
return "左";
}
},
RIGHT("右"){
@Override
public String show() {
return "右";
}
};
private String name;
private DirectionDemo(String name){
this.name = name;
}
public String getName(){
return name;
}
public abstract String show();
}
public class Demo {
public static void main(String[] args) {
DirectionDemo up = DirectionDemo.UP;
System.out.println(up);
System.out.println(up.getName());
System.out.println(up.show());
DirectionDemo down = DirectionDemo.DOWN;
System.out.println(down);
System.out.println(down.getName());
System.out.println(down.show());
System.out.println("------------------");
System.out.println(up.ordinal()); //0
System.out.println(down.ordinal()); //1
System.out.println(up.compareTo(down)); //-1
System.out.println(up.name()); //UP
System.out.println(up.toString()); //UP
DirectionDemo[] values = DirectionDemo.values();
for (DirectionDemo value : values) {
System.out.println(value);
}
}
}
案例:在switch中使用枚举
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
public class Demo {
public static void main(String[] args) {
Season s = Season.AUTUMN;
switch(s){
case SPRING:
System.out.println("春天到了");
break;
case SUMMER:
System.out.println("夏天到了");
break;
case AUTUMN:
System.out.println("秋天到了");
break;
case WINTER:
System.out.println("冬天到了");
break;
}
}
}
案例:在switch中使用枚举
```java
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
public class Demo {
public static void main(String[] args) {
Season s = Season.AUTUMN;
switch(s){
case SPRING:
System.out.println("春天到了");
break;
case SUMMER:
System.out.println("夏天到了");
break;
case AUTUMN:
System.out.println("秋天到了");
break;
case WINTER:
System.out.println("冬天到了");
break;
}
}
}