小白学习Java第十九天

复习

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之线程优先级
  1. final int getPriority(): 返回此线程的优先级

  2. final void setPriority(int newPriority): 更改此线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10
    Thread类提供的优先级常量(静态常量)
    MIN_PRIORITY = 1; 最低
    NORM_PRIORITY = 5; 默认
    MAX_PRIORITY = 10; 最高

1.2线程API之后台线程
  1. 后台线程也叫作守护线程。守护是用来提供服务的。

  2. 守护线程的特点:如果一个程序的运行中,没有非守护线程了,只有守护线程了,那么守护线程会过一段时间后自动停止

  3. 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张票的销售过程利用多线程实现出来

一. 实现步骤:
  1. 定义一个类SaleTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

  2. 在SaleTicket类中重写run()方法实现卖票,代码步骤如下
    (1) 判断票数大于0,就卖票,并告知是哪个窗口卖的
    (2) 卖了票之后,总票数要减1
    (3) 票卖没了,线程停止
    (4) 定义一个测试类SaleTicketDemo,里面有main方法,代码步骤如下
    (5) 创建SaleTicket类的对象
    (6) 创建三个Thread类的对象,把SaleTicket对象作为构造方法的参数,并给出对应的窗口名称
    (7) 启动线程

二. 案例中出现的问题
1. 相同的票出现了多次
2. 出现了负数的票

在这里插入图片描述

三. 问题发生原因:

线程执行的随机性导致的安全问题,线程卖票过程中随时可能丢失cpu的执行权,导致当前线程没有执行完就因为没有资源而被迫暂停, 当资源再次回归该线程时,代码中的数据可能可能已经与之前不同, 因此出现了数据安全问题
在这里插入图片描述

四. 解决方案

保证线程中代码执行的完整性, 原子性. 原子操作就是不可分割的操作,例如售票的过程中的代码就是一个不可分割的操作

2.2同步代码块
  1. 同步代码块:
    这种格式,可以确保cpu在执行A线程的时候,不会切换到影响A线程执行的其他线程上去

  2. 使用格式

    synchronized (锁对象) {
     需要保证完整性、原子性的代码;
     }
    

说明 :

  1. synchronized : 同步的概念,关键字
  2. 小括号中,可以设计任意一个对象, 但是保证是多个线程共享的唯一对象, 这个对象的作用就像一把锁, 将同步代码块中的代码锁住不被其他线程打扰
  3. 将需要保证完整执行的代码,设计在同步代码块中
  1. 使用同步代码块之后的效果:
    当cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;当cpu正在执行当前代码块的内容时,cpu可以切换到其他线程,但是不能切换到需要相同锁对象的线程上。
    当cpu执行完当前代码块中的代码之后,就会释放锁对象,cpu就可以运行其他需要当前锁对象的同步代码块了

  2. 弊端:
    当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

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. 格式:
    (1) 同步成员方法:

     修饰符 synchronized 返回值类型 方法名(方法参数) { 
     方法体;
     }
    

(2) 同步静态方法:

修饰符 static synchronized 返回值类型 方法名(方法参数) { 
    方法体;
}
  1. 两种同步方法的锁对象
    (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锁

  1. 概述: 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

  2. Lock 提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
    Lock锁提供了获取锁和释放锁的方法
    1)void lock(): 获得锁
    2)void unlock(): 释放锁

  3. 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()
 * */

(四) 死锁现象

  1. 死锁的发生: 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程均处于等待状态,无法前往执行

在这里插入图片描述

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(){}
*      }
* */

  1. 死锁诊断(jstack工具的使用)
  1. 打开命令运行窗口
    在这里插入图片描述

  2. 执行命令 : jps ,表示输出JVM中运行的进程状态信息,从而获取pid,即进程编号

在这里插入图片描述

  1. 指定命令: jstack 指定进程pid, 查看某个Java进程内的某个线程堆栈信息

在这里插入图片描述

命令执行后的效果:

在这里插入图片描述

(五)枚举

一. 枚举的定义特点以及常用方法
(一) 枚举的概述
  1. 为了间接的表示一些固定的值,Java就给我们提供了枚举,是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内
  2. 枚举本质上来说就是对象的内容和个数已经确定了的类
    枚举项:就是枚举类当中的一个一个的被确定了的对象
    例如:星期类,只有周一到周日7个对象; 月份类,只有1-12月这12个对象;线程的状态Thead.State,只有6种状态,这些都可以使用枚举类型
(二) 枚举定义格式
  1. 枚举类型使用 enum 关键字定义
    定义格式:

     public enum 枚举名{   
     枚举项1,枚举项2,枚举项3;
     }
    
  2. 枚举的使用场景
    有些情况下,类型中可以创建的对象的个数是固定的,就可以使用枚举类型

  3. 枚举类型,源文件.java , 编译后的文件.class

(三) 枚举的特点
  1. 所有枚举类都是Enum的子类
  2. 我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
  3. 每一个枚举项其实就是该枚举的一个对象, 枚举项写作方式: 枚举对象名称,枚举对象名称,…最后一个对象名称;
  4. 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的内容,这个分号就不能省略。建议不要省略
  5. 枚举也是一个类,也可以去定义成员变量
  6. 枚举类可以有构造器,但必须是private的,它默认的也是private的.
  7. 枚举类也可以有抽象方法,但是枚举项必须重写该方法
(四) 枚举类型中的常用方法
  1. ordinal(): 获取枚举类型中的枚举序数,序数根据定义的枚举项,从0开始,返回值int
  2. compareTo(E o) : 比较枚举项之间的顺序大小,方法调用枚举项的序数减去参数枚举项的序数
  3. name() : 将枚举项转换成String类型
  4. toString() : 将枚举项转换成String类型
  5. static T valueOf(Class type,String name) 是Enum类的静态方法,获取指定枚举类中的指定名称的枚举值
  6. 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值