Skip to content

day15.多线程

java
课前回顾:
  1.Math数学工具类:
    abs ceil floor round max min
  2.BigInteger:处理超大整数
    add subtract multiply divide
  3.BigDecimal:处理float和double直接参与运算而出现的精度损失问题
    valueOf add subtract multiply divide
    a.注意:除不尽,会出现"运算异常"
    b.解决: divide(除号后面的BigDecimal对象,保留几位小数,取舍方式)
  4.Date:日期类
    Date() Date(long time)
    setTime getTime
  5.Calendar日历类:
    getInstance   get  set  add getTime
  6.SimpleDateFormat:日期格式化类
    a.构造: SimpleDateFormat(String pattern)
    b.方法: format  parse
  7.jdk8新日期类:
    LocalDate:操作年月日的日期对象
    LocalDateTime:操作年月日时分秒的日期对象

    get开头 -> 获取日期字段
    with开头 -> 设置日期字段
    plus开头 -> 向后偏移
    minus开头 -> 向前偏移

  8.设置时间偏差
    Period:计算年月日时间偏差
           between
    Duration:计算精准时间偏差
           between

  9.DateTimeFormatter:日期格式化类
    ofPattern
    format
    parse

 10.System类:
    currentTimeMills
    exit
    arrayCopy
 11.Arrays类:
    toString  sort binarySearch  copyOf
今日重点:
  0.会手动拆箱和装箱
  1.会定义一个标准的javabean
  2.会使用继承Thread的方式实现多线程程序
  3.会使用实现Runnable的方式实现多线程程序
  4.会使用同步代码块以及同步方法实现线程安全问题

第一章.包装类

1.基本数据类型对应的引用数据类型(包装类)

java
1.概述:基本数据类型对应的引用类型-> 包装类
2.为啥要用包装类:
  将来我们在操作数据的时候或者调用方法的时候,人家要求用引用类型,不让直接传递基本类型的数据,此时我们就需要传递基本类型对应的包装类型
  比如: ArrayList<Integer> list = new ArrayList<Integer>()

  再比如: add(Integer i)
基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2.Integer 的介绍以及使用

2.1.Integer 基本使用

java
1.创建:
  Integer(int value)
  Integer(String s)   s必须是数字格式 "1"
java
public class Demo01Integer {
    public static void main(String[] args) {
        Integer i1 = new Integer(111);
        System.out.println(i1);

        Integer i2 = new Integer("111");
        System.out.println("i2 = " + i2);
    }
}
java
1.装箱:将基本类型转成对应的包装类
2.方法:Integer中的方法 -> static Integer valueOf(int i)
3.为啥装箱:将来操作某个方法,或者存某个数据,人家要求使用包装类,此时我们就需要装箱
java
public class Demo02Integer {
    public static void main(String[] args) {
        Integer i1 = Integer.valueOf(111);
        System.out.println("i1 = " + i1);
    }
}
java
1.拆箱:将包装类转成对应的基本类型
2.方法:Integer中的方法
  int intValue()
3.为啥要拆箱:包装类不能直接用+ - * /等符号所以,需要先转成基本类型才能直接运算
java
public class Demo02Integer {
    public static void main(String[] args) {
        Integer i1 = Integer.valueOf(111);
        System.out.println("i1 = " + i1);

        int i = i1.intValue();
        System.out.println(i+1);
    }
}

2.2.自动拆箱装箱

java
将来一般情况下都是自动拆箱和装箱
java
public class Demo03Integer {
    public static void main(String[] args) {
        Integer i = 100;//自动装箱
        /*
           先将i拆箱,然后和10做运算
           得出一个结果,再重新装箱
           重新赋值给Integer的i
         */
        i+=10;
        System.out.println(i);
    }
}
1743472081945
java
public class Demo04Integer {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        System.out.println(i1 == i2);//true

        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i3 == i4);//false
    }
}
1743464218027

3.基本类型和 String 之间的转换

3.1 基本类型往 String 转

java
1.方式1: + 拼接
2.方式2: String中的静态方法:
  String valueOf(int i)
java
public class Demo05Integer {
    public static void main(String[] args) {
        int i = 10;
        String s1 = i+"";
        System.out.println(s1);

        String s2 = String.valueOf(i);
        System.out.println("s2 = " + s2);
    }
}

3.2 String 转成基本数据类型

java
每一个包装类中都有一个类似的方法:parseXXX()
位置方法说明
Bytestatic byte parseByte(String s)将字符串转成 byte 类型
Shortstatic short parseShort(String s)将字符串转成 short 类型
Integerstatic int parseInt(String s)将字符串转成 int 类型
Longstatic long parseLong(String s)将字符串转成 long 类型
Floatstatic float parseFloat(String s)将字符串转成 float 类型
Doublestatic double parseDouble(String s)将字符串转成 double 类型
Booleanstatic boolean parseBoolean(String s)将字符串转成 boolean 类型
java
public class Demo06Integer {
    public static void main(String[] args) {
        int i = Integer.parseInt("111");
        System.out.println(i+1);
    }
}
java
1.注意:将来定义标准的javabean,javabean类中的属性,如果是基本类型的,需要使用对应的包装类定义
2.原因:
  a.后面的框架都是使用包装类
  b.将来我们javabean都是根据表来定义的,我们每一张表都应该有一个主键 ,主键一般都是自增长的
    如果主键是自增长的,我们添加数据的时候,不需要我们自己维护主键列的数据,mysql会自动为每一条数据编号
    insert into user values (null,'xiaoming','444')

    我们javabean中的主键值不需要赋值,直接使用默认值,将其添加到insert语句中,然后数据库会根据null自动编号
java
public class User {
    private Integer uid;//null
    private String username;//null
    private String password;//null

    public User() {
    }

    public User(Integer uid, String username, String password) {
        this.uid = uid;
        this.username = username;
        this.password = password;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
1743475751411

第二章.多线程基本了解

1.多线程_线程和进程

java
进程:进入到内存执行的应用程序
1743476130859
java
1.线程:进程中的一个执行单元
2.线程作用:负责当前进程中程序的运行.一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序
3.简单理解:进程中的一个功能就需要一条线程去执行
1743476558810

使用场景:软件中的耗时操作 -> 拷贝大文件, 加载大量的资源

​ 所有的聊天软件

​ 所有的后台服务器

多线程程序同时干多件事儿,提高了 CPU 使用率

2.并发和并行

java
并行:在同一个时刻,有多个指令在多个CPU上(同时)执行(好比是多个人做不同的事儿)
    比如:多个厨师在炒多个菜
1743477914906
java
并发:在同一个时刻,有多个指令在单个CPU上(交替)执行
    比如:一个厨师在炒多个菜
1743478000803
java
细节:
  1.之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换
  2.现在咱们的CPU都是多核多线程的了,比如2核4线程,那么CPU可以同时运行4个线程,此时不用切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在

3.CPU 调度

java
1.分时调度:让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
2.抢占式调度:多个线程抢占CPU使用权,哪个线程优先级越高,先抢到CPU使用权的几率就大,但是不是说每次先抢到CPU使用权的都是优先级高的线程,只是优先级高的线程先抢到CPU使用权的几率会大一些 -> java代码

4.主线程介绍

java
1.专门为main方法服务的线程 -> 主线程
1743478760183

第三章.创建线程的方式(重点)

1.第一种方式_extends Thread

java
1.定义一个自定义线程类,继承Thread类
2.重写Thread中的run方法,在run方法中设置线程任务(该线程要干的事儿,要执行的代码)
3.创建自定义线程类对象
4.调用Thread类中的start方法(开启线程,jvm会自动执行run方法)
java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread...执行了"+i);
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        //开启自定义线程类对象
        MyThread t1 = new MyThread();
        //调用start方法,开启线程,jvm自动执行run方法
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程main...执行了"+i);
        }
    }
}

注意:如果直接调用 run 方法,并不代表将线程开启,仅仅是简单的调用方法

​ 只有调用 start 方法,线程才会真正开启

2.多线程在内存中的运行原理

1743487682008
java
同一个线程对象,不要连续调用多次start方法;如果想开启新线程,就需要重新new一个线程对象

3.Thread 类中的方法

java
void start() -> 开启线程,jvm自动调用run方法
void run()  -> 设置线程任务,这个run方法是Thread重写的接口Runnable中的run方法
String getName()  -> 获取线程名字
void setName(String name) -> 给线程设置名字
static Thread currentThread() -> 获取正在执行的线程对象(此方法在哪个线程中使用,获取的就是哪个线程对象)
static void sleep(long millis)->线程睡眠,超时后自动醒来继续执行,传递的是毫秒值
java
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        //开启自定义线程类对象
        MyThread t1 = new MyThread();
        //给t1设置线程名字
        t1.setName("赵四");
        //调用start方法,开启线程,jvm自动执行run方法
        t1.start();

        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000L);
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}
java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //线程睡眠
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

在 run 方法中有异常只能 try,不能 throws,因为 Thread 中的 run 方法没有 throws,重写之后就不能 throws

4.Thread 中其他的方法

java
void setPriority(int newPriority)   -> 设置线程优先级,优先级越高的线程,抢到CPU使用权的几率越大,但是不是每次都先抢到

int getPriority()  -> 获取线程优先级

void setDaemon(boolean on)  -> 设置为守护线程,当非守护线程执行完毕,守护线程就要结束,但是守护线程也不是立马结束,当非守护线程结束之后,系统会告诉守护线程人家结束了,你也结束吧,在告知的过程中,守护线程会执行,只不过执行到半路就结束了

static void yield() -> 礼让线程,让当前线程让出CPU使用权

void join() -> 插入线程或者叫做插队线程

4.1.线程优先级

java
public class Test01 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("金莲");
        MyThread t2 = new MyThread();
        t2.setName("大郎");
        //System.out.println(t1.getPriority());//默认都是5
        //System.out.println(t2.getPriority());//默认都是5

        //设置优先级
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);

        t1.start();
        t2.start();

    }
}
java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

4.2.守护线程

java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}
java
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("金莲");

        MyThread1 t2 = new MyThread1();
        t2.setName("大郎");

        //给t2设置为守护线程
        t2.setDaemon(true);

        t1.start();
        t2.start();

    }
}
1743490489121

4.3.礼让线程

java
场景说明:如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B再执行
        那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行
注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了
java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
            //设置礼让线程
            Thread.yield();
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("金莲");
        MyThread t2 = new MyThread();
        t2.setName("涛哥");

        t1.start();
        t2.start();
    }
}

4.4.插入线程

java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.setName("金莲");
        t1.start();

       /*
           join(),插入线程
           将t1插入到当前线程前面,现在只有两条线程,一个t1,一个主线程
           我们想将t1插入到主线程前面,主线程就是当前线程
         */

        t1.join();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

5.第二种方式_实现 Runnable 接口

java
1.定义一个类MyRunnable,实现Runnable接口
2.重写run方法,设置线程任务
3.创建MyRunnable对象,然后再创建Thread对象,将MyRunnable对象当参数传递到Thread对象中
  Thread(Runnable target)

4.调用Thread中的start方法,开启线程,jvm自动执行run方法
java
public class Test01 {
    public static void main(String[] args) {
        //创建实现类对象
        MyRunnable myRunnable = new MyRunnable();
        /*
            创建Thread对象,传入实现类对象
            a.Thread(Runnable r)
            b.Thread(Runnable r,String name) 可以设置线程名字
         */
        Thread t1 = new Thread(myRunnable,"赵四");
        //开启线程
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}
java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

6.两种实现多线程的方式区别

java
1.继承方式:有局限性的,因为继承只支持单继承,如果一个线程类还需要继承一个其他的父类,此时就不能继承其他的父类了

2.接口方式:解决了单继承的局限性,因为一个子类继承一个父类的同时还可以实现一个或者多个接口

实现多线程的方式有 4 种:

1.继承 Thead

2.实现 Runnable 接口

3.实现 Callable 接口

4.线程池方式实现

7.匿名内部类创建多线程

java
public class Test02 {
    public static void main(String[] args) {
        /*
           new Thread(Runnable r)
           new Thread(Runnable r,String name)

         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"...执行了"+i);
                }
            }
        },"赵四").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"...执行了"+i);
                }
            }
        },"刘能").start();
    }
}

第四章.线程安全

java
出现线程不安全的原因:多个线程同时访问同一个资源

1.线程安全问题-->线程不安全的代码

java
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "广坤");
        Thread t2 = new Thread(myTicket, "赵四");
        Thread t3 = new Thread(myTicket, "刘能");
        t1.start();
        t2.start();
        t3.start();
    }
}
java
public class MyTicket implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        while(true){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

2.解决线程安全问题的第一种方式(使用同步代码块)

java
1.格式:
  synchronized(锁对象){
      可能出现的线程不安全的代码
  }

2.执行流程:
  线程抢到所之后进入到同步代码块中执行,其他线程就需要在外面等待,等待在同步代码块中的代码执行完毕,出了同步代码块,相当于释放锁了,等待的线程才会有机会抢到锁去执行

3.注意:锁对象是任意对象,但是多个线程之间必须共享同一把锁对象
java
public class MyTicket implements Runnable {
    int ticket = 100;

    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "广坤");
        Thread t2 = new Thread(myTicket, "赵四");
        Thread t3 = new Thread(myTicket, "刘能");
        t1.start();
        t2.start();
        t3.start();
    }
}

3.解决线程安全问题的第二种方式:同步方法

3.1.普通同步方法_非静态

java
1.格式:
  修饰符 synchronized 返回值类型 方法名(形参){
      可能出现的线程不安全代码
  }

2.默认锁:this
java
public class MyTicket implements Runnable {
    int ticket = 100;

    //Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            method();
        }
    }

    public synchronized void method(){
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
            ticket--;
        }
    }

/*    public void method(){
        synchronized (this){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                ticket--;
            }
        }
    }*/
}
java
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "广坤");
        Thread t2 = new Thread(myTicket, "赵四");
        Thread t3 = new Thread(myTicket, "刘能");
        t1.start();
        t2.start();
        t3.start();

    }
}

3.2.静态同步方法

java
1.格式:
  修饰符 static synchronized 返回值类型 方法名(形参){
      可能出现的线程不安全代码
  }

2.默认锁:当前类.class -> 获取类的class对象,class对象只有一个
java
public class MyTicket implements Runnable {
    static int ticket = 100;

    //Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            method();
        }
    }

    public static synchronized void method(){
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
            ticket--;
        }
    }

/*    public static void method(){
        synchronized (MyTicket.class){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                ticket--;
            }
        }
    }*/
}
java
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "广坤");
        Thread t2 = new Thread(myTicket, "赵四");
        Thread t3 = new Thread(myTicket, "刘能");
        t1.start();
        t2.start();
        t3.start();

    }
}

第五章.死锁

1.死锁介绍(锁嵌套就有可能产生死锁)

java
指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁
1725839050045
java
根据上图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行
而线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中

2.死锁的分析

1743498510989

3.代码实现

java
public class LockA {
    public static LockA lockA = new LockA();
}
java
public class LockB {
    public static LockB lockB = new LockB();
}
java
public class DieLock implements Runnable {
    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (LockA.lockA) {
                System.out.println("if...lockA");
                synchronized (LockB.lockB) {
                    System.out.println("if...lockB");
                }
            }
        }else{
            synchronized (LockB.lockB) {
                System.out.println("else...lockB");
                synchronized (LockA.lockA) {
                    System.out.println("else...lockA");
                }
            }
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);
        Thread t1 = new Thread(dieLock1);
        Thread t2 = new Thread(dieLock2);
        t1.start();
        t2.start();
    }
}

只需要知道原因即可