复习
1.概念
进程和线程
进程:正在运行的程序,需要两个资源的支持:CPU和内存
线程:可以独立运行的代码片段。前提是:必须在进程中。
一个进程中可以有多个独立运行的执行单元—多线程
一个进程至少有一个执行单元—之前的代码
主线程:main方法中封装的代码
子线程:run方法中封装的代码
并发和并行
并发:多个任务同时发起,在某一时刻只有一个任务在执行,CPU在做着切换。
并行:多个任务同时发在,在每个时刻每个任务都在执行。
2.线程创建的方式
1.继承Thread类
1.定义类继承Thread 2.重写run方法 3.创建子类对象 4.调用start方法
2.实现接口Runnable
1.定义类实现Runnable 2.重写run方法 3.创建实现类对象 4.创建线程对象,并传递实现类对象 5.调用start方法
3.实现接口Callable (jdk5.0版本)
1.定义类实现Callable 2.重写call方法 3.创建实现类对象 4.创建FutureTask对象,并传递实现类对象 5.创建线程对象,传递FutureTask对象(该对象实现了Runnable接口) 6.调用start方法
3.常用方法
1.获取线程对象 static currentThread()
2.设置和获取线程名称:
构造方法: new Thread(name)
实例方法: setName()
实例方法: getName()
3.睡眠 static sleep(毫秒值)
4.礼让 static yield()
4.Properties:
setProperty(key,value)
load(InputStream/Reader i)
store(OutputStream/Writer w)
课程
一. 多线程
(一) Thread线程常用方法
1.1线程API之线程优先级
-
final int getPriority(): 返回此线程的优先级
-
final void setPriority(int newPriority): 更改此线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10
Thread类提供的优先级常量(静态常量)
MIN_PRIORITY = 1; 最低
NORM_PRIORITY = 5; 默认
MAX_PRIORITY = 10; 最高
1.2线程API之后台线程
-
后台线程也叫作守护线程。守护是用来提供服务的。
-
守护线程的特点:如果一个程序的运行中,没有非守护线程了,只有守护线程了,那么守护线程会过一段时间后自动停止
-
final void setDaemon(boolean on) : 参数设置为true,将线程设置为守护线程
将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出
类比举例:
下象棋:
非守护线程 : 将/帅, 保证自己的运行
守护线程 : 兵,炮,马,车…, 所有这些存在是为了守护帅/将正常运行
public class Demo1 {
public static void main(String[] args) {
MyRun my = new MyRun();
Thread t = new Thread(my);
// t.setPriority(Thread.MAX_PRIORITY); //设置优先级最大值
// t.setDaemon(true); //子线程为守护线程
t.start();
System.out.println(t.isDaemon()); //子线程是否是守护线程
// System.out.println(t.getPriority()+"...."); //获取子线程优先级,默认
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName()+"--------"+i);
}
// System.out.println(Thread.currentThread().getPriority()); //获取主线程的优先级,默认
// System.out.println(Thread.currentThread().isDaemon()); //主线程是否是守护线程
}
}
class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName()+"....."+i);
// throw new NullPointerException();
}
}
}
/*
* 无论是主线程还是子线程都有优先级,默认都是5.
* 级别:1-10.
* 通常情况下,优先级高的线程被cpu调度的可能性变大。
* 优先级设置:
* 找极端值设置,要么最大10,要么最小1,要么取中间值5
* setPriority() getPriority()
*
*
* 守护线程:
* 线程可以设置为守护线程也可以设置为非守护线程(守护线程也叫做后台线程,用户线程)
* 默认线程是非守护线程。
* 守护线程是依赖于非守护线程而存在的,也就是非守护在,守护在,非守护消失,守护消失。
*
* 注意:
* 多线程并发运行时,某个线程出现异常,对其他线程没有影响,其他线程可以继续运行,有异常的线程终止执行。
*
* */
(二) 线程安全问题
2.1线程安全问题描述
需求: 影院在进行影票销售,销售影票<葫芦娃救爷爷>,可以团团, 猫猫 ,影院线下三种渠道购票 , 三个渠道共销售100张票,一张票只能一个渠道销售, 请将100张票的销售过程利用多线程实现出来
一. 实现步骤:
-
定义一个类SaleTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
-
在SaleTicket类中重写run()方法实现卖票,代码步骤如下
(1) 判断票数大于0,就卖票,并告知是哪个窗口卖的
(2) 卖了票之后,总票数要减1
(3) 票卖没了,线程停止
(4) 定义一个测试类SaleTicketDemo,里面有main方法,代码步骤如下
(5) 创建SaleTicket类的对象
(6) 创建三个Thread类的对象,把SaleTicket对象作为构造方法的参数,并给出对应的窗口名称
(7) 启动线程
二. 案例中出现的问题
1. 相同的票出现了多次
2. 出现了负数的票
三. 问题发生原因:
线程执行的随机性导致的安全问题,线程卖票过程中随时可能丢失cpu的执行权,导致当前线程没有执行完就因为没有资源而被迫暂停, 当资源再次回归该线程时,代码中的数据可能可能已经与之前不同, 因此出现了数据安全问题
四. 解决方案
保证线程中代码执行的完整性, 原子性. 原子操作就是不可分割的操作,例如售票的过程中的代码就是一个不可分割的操作
2.2同步代码块
-
同步代码块:
这种格式,可以确保cpu在执行A线程的时候,不会切换到影响A线程执行的其他线程上去 -
使用格式
synchronized (锁对象) { 需要保证完整性、原子性的代码; }
说明 :
- synchronized : 同步的概念,关键字
- 小括号中,可以设计任意一个对象, 但是保证是多个线程共享的唯一对象, 这个对象的作用就像一把锁, 将同步代码块中的代码锁住不被其他线程打扰
- 将需要保证完整执行的代码,设计在同步代码块中
-
使用同步代码块之后的效果:
当cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;当cpu正在执行当前代码块的内容时,cpu可以切换到其他线程,但是不能切换到需要相同锁对象的线程上。
当cpu执行完当前代码块中的代码之后,就会释放锁对象,cpu就可以运行其他需要当前锁对象的同步代码块了 -
弊端:
当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class Demo2 {
public static void main(String[] args) {
MyTicket my = new MyTicket();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
class MyTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
synchronized (this){ //锁:开默认 关 持有锁:将状态由开改为关
if(ticket > 0){
/*Thread.sleep(10000); */ //休眠状态的线程不会释放锁
System.out.println(Thread.currentThread().getName()+"....."+ticket);
ticket--;
}
} //释放锁,并将锁由关改为开
}
}
}
/*
* 多线程间的安全问题:
* 窗口售票案例。一共有100张,分多个窗口进行售卖。
* 运行代码:出现了两个窗口卖相同的票,售卖错误的票出现0或-1...
*
* 分析原因:
* 1.多线程运行的代码是相同的,两个窗口操作的是同一个共享资源数据
* 2.某个线程在对ticket进行操作时,还没有全部操作完,另一个线程参与进来执行,导致出现了错误的数据
* 总结:
* 多线程操作共享资源,共享资源代码是由多行来完成的,其中一个线程还没有对其操作完,另一个线程参与进来执行。
* 解决方案:
* 共享资源代码被某个线程执行时,另一个线程不能参与执行
* 同步代码块
* synchronized(锁对象){
* 共享资源代码
* }
* 注意:
* 1.多线程看到的必须是同一个锁对象
* 2.共享资源必须在同步中
* 3.必须两个或两个以上的线程使用同步
* 4.锁对象可以是任意对象
* 不同方法
* 显示锁
*
* 好处: 解决了安全问题
* 弊端: 效率降低了
* */
2.3同步方法
-
格式:
(1) 同步成员方法:修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
(2) 同步静态方法:
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
- 两种同步方法的锁对象
(1) 同步成员方法的锁对象是 this
(2) 同步静态方法的锁对象是 类名.class
public class Demo3 {
public static void main(String[] args) {
MyTicket1 my = new MyTicket1();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
class MyTicket1 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
showTicket();
}
}
//同步方法
public synchronized void showTicket(){
if (ticket > 0) {
/*Thread.sleep(10000); */ //休眠状态的线程不会释放锁
System.out.println(Thread.currentThread().getName() + "....." + ticket);
ticket--;
}
}
}
/*
* synchronized:
* 修饰代码块,修饰方法
* (代码块: 局部、构造、静态、同步)
* 修饰方法: 同步方法,具有锁。
* 实例同步方法: 通过对象调用,锁对象是:this
* 静态不同方法: 通过类名调用,锁对象是:类名.class (静态方法所属的类的字节码文件对象)
* */
(三) Lock锁
-
概述: 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
-
Lock 提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Lock锁提供了获取锁和释放锁的方法
1)void lock(): 获得锁
2)void unlock(): 释放锁 -
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock构造方法: ReentrantLock()创建一个ReentrantLock的实例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
public static void main(String[] args) {
MyTicket2 my = new MyTicket2();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
class MyTicket2 implements Runnable {
private int ticket = 100;
//创建lock对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
try {
if (ticket > 0) {
/*Thread.sleep(10000); */ //休眠状态的线程不会释放锁
System.out.println(Thread.currentThread().getName() + "....." + ticket);
ticket--;
}
} finally {
//释放锁
lock.unlock();;
}
}
}
}
/*
* jdk5.0版本:
* Lock锁对象
* 显示加锁 : lock()
* 显示释放锁: unlock()
* */
(四) 死锁现象
- 死锁的发生: 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程均处于等待状态,无法前往执行
public class Demo5 {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLock(true));
Thread t2 = new Thread(new DeadLock(false));
t1.start();
t2.start();
}
}
class MyLock{
static Object A = new Object();
static Object B = new Object();
}
class DeadLock implements Runnable{
private boolean flag ;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
// while(true){
synchronized (MyLock.A){
System.out.println("if---A");
synchronized (MyLock.B){
System.out.println("if---B");
}
}
// }
}else{
// while(true){
synchronized (MyLock.B){
System.out.println("else---A");
synchronized (MyLock.A){
System.out.println("else---B");
}
}
// }
}
}
}
/*
* 死锁:
* 两个或两个以上的线程并发启动,各自线程持有对方线程想要的资源,但是持有资源的线程不释放持有的资源,不能继续向下执行,
* 程序又不能停止,出现了所谓“卡”的现象。
* 死锁不一定是百分百发生,但是发生了用户体验不好,因此实际开发中不能出现死锁。
* 同步的嵌套容易出现死锁,实际中不建议使用同步嵌套。
* synchronized(){
* synchronized(){}
* }
* */
- 死锁诊断(jstack工具的使用)
-
打开命令运行窗口
-
执行命令 : jps ,表示输出JVM中运行的进程状态信息,从而获取pid,即进程编号
- 指定命令: jstack 指定进程pid, 查看某个Java进程内的某个线程堆栈信息
命令执行后的效果:
(五)枚举
一. 枚举的定义特点以及常用方法
(一) 枚举的概述
- 为了间接的表示一些固定的值,Java就给我们提供了枚举,是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内
- 枚举本质上来说就是对象的内容和个数已经确定了的类
枚举项:就是枚举类当中的一个一个的被确定了的对象
例如:星期类,只有周一到周日7个对象; 月份类,只有1-12月这12个对象;线程的状态Thead.State,只有6种状态,这些都可以使用枚举类型
(二) 枚举定义格式
-
枚举类型使用 enum 关键字定义
定义格式:public enum 枚举名{ 枚举项1,枚举项2,枚举项3; }
-
枚举的使用场景
有些情况下,类型中可以创建的对象的个数是固定的,就可以使用枚举类型 -
枚举类型,源文件.java , 编译后的文件.class
(三) 枚举的特点
- 所有枚举类都是Enum的子类
- 我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
- 每一个枚举项其实就是该枚举的一个对象, 枚举项写作方式: 枚举对象名称,枚举对象名称,…最后一个对象名称;
- 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的内容,这个分号就不能省略。建议不要省略
- 枚举也是一个类,也可以去定义成员变量
- 枚举类可以有构造器,但必须是private的,它默认的也是private的.
- 枚举类也可以有抽象方法,但是枚举项必须重写该方法
(四) 枚举类型中的常用方法
- ordinal(): 获取枚举类型中的枚举序数,序数根据定义的枚举项,从0开始,返回值int
- compareTo(E o) : 比较枚举项之间的顺序大小,方法调用枚举项的序数减去参数枚举项的序数
- name() : 将枚举项转换成String类型
- toString() : 将枚举项转换成String类型
- static T valueOf(Class type,String name) 是Enum类的静态方法,获取指定枚举类中的指定名称的枚举值
- static values() : 将一个枚举类型中的所有枚举项获取到,返回值类型枚举类型的数组
public class Demo6 {
public static void main(String[] args) {
Level a = Level.A;
a.show();
}
}
enum Level{
A(5){
@Override
public void method() {
}
}; //匿名子类对象
//,B(),C(),D(),E();
int num;
private Level(){
System.out.println("执行了");
}
private Level(int num){
System.out.println("执行了---"+num);
this.num = num;
}
public void show(){
System.out.println("level ---show");
}
//抽象方法
public abstract void method();
}
/*
//描述一类成绩等级事物 ,定义类
//等级一共默认5个, 该类能创建的对象的个数是5个
//默认定义的类,可以创建无数个对象,怎么控制对象的个数?
//1.构造方法私有化 2.在类中创建5个对象,对外暴露
class Level{
int num;
public static Level A = new Level(6);
public static Level B = new Level(7);
public static Level C = new Level(8);
public static Level D = new Level();
public static Level E = new Level();
private Level(){}
private Level(int num){
System.out.println("执行了---"+num);
this.num = num;
}
public void show(){
System.out.println("show");
}
}*/
/*
* 引用类型:
* 数组 类 接口 枚举(jdk5.0) 注解
*
* 枚举类型:
* 某个类型的个数是有限的,使用枚举来定义。
* 格式:
* 修饰符 enum 枚举类名{
* 枚举值名,枚举值名,枚举值名,...;
* }
* 枚举类中的每个枚举值实际就是该类的一个实例对象,通过枚举类名.直接访问。
* 注意:枚举类编译后也是class文件,因此可以认为枚举类也是一个特殊的类
*
* 枚举类中的成员:
* 1. 变量 2.方法 3.构造方法(私有化) 4.抽象方法(必须重写)
* 什么时候使用枚举类?
* 某个类型的对象是有限的或可穷举的,如:性别、星期、月份、季节、交通灯...
* 枚举类名:通常用大写单词或字母来表示,即合法标识符。
* */
一些方法
案例 : 使用枚举类型, 模拟星期使用, 星期类型一共可以创建出7个对象,表示星期一—星期日
public class Demo7 {
public static void main(String[] args) {
Week mon = Week.MON;
System.out.println(mon.name()); //获取枚举值名称
System.out.println(mon.toString());//获取枚举值名称
System.out.println(mon.ordinal()) ;//获取枚举值序号,从0开始
System.out.println(mon.compareTo(Week.SUN)); //比较两个枚举值的序号差值
System.out.println("---------------------------------------------");
//获取所有的枚举值
Week[] values = Week.values();
for (Week week : values)
System.out.println(week);
System.out.println("-----------------------------------");
//switch语句表达式结果类型:byte char int String 枚举 short
switch (Week.WEN){
case MON:
System.out.println("星期一");
break;
case WEN:
System.out.println("星期三");
break;
case SUN:
System.out.println("星期日");
}
}
}
enum Week{
MON,TUS,WEN,THU,FRI,SAT,SUN;
}