Java Thread多线程 笔记+作业

引例:单线程不能满足"同时"的需求

假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入exit)

单线程

无法满足同时的需求

package _19thread01.com.cskaoyan._01introduction;

import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/27 15:54
 **/

/*
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
单线程的
 */
public class Demo {
   
    public static boolean flag = true;
    public static void main(String[] args) {
   
        System.out.println("say hello before");
        sayHello();
        System.out.println("say hello after");
        System.out.println("wait before");

        waitToStop();
        System.out.println("wait after");

    }

    private static void waitToStop() {
   
        // 创建Scanner对象
        Scanner scanner = new Scanner(System.in);
        // 循环
        while (flag) {
   
            // 键盘接收数据
            String s = scanner.nextLine();
            // 判断
            if ("gun".equals(s)) {
   
                // 结束 更改flag值
                flag = false;
                break;
            }

        }


    }

    private static void sayHello() {
   
        // 不停输出你好
        while (flag) {
   
            System.out.println("你好");
            // 休息一下
            try {
   
                // 暂停程序执行3s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }

        }
    }
}

多线程改进

package _19thread01.com.cskaoyan._01introduction;

import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/27 15:54
 **/

/*
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
多线程改进
 */
public class Demo2 {
   
    public static boolean flag = true;
    public static void main(String[] args) {
   
        System.out.println("say hello before");
        sayHello();
        System.out.println("say hello after");
        System.out.println("wait before");

        waitToStop();
        System.out.println("wait after");

    }

    private static void waitToStop() {
   
        new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                // 创建Scanner对象
                Scanner scanner = new Scanner(System.in);
                // 循环
                while (flag) {
   
                    // 键盘接收数据
                    String s = scanner.nextLine();
                    // 判断
                    if ("gun".equals(s)) {
   
                        // 结束 更改flag值
                        flag = false;
                        break;
                    }

                }
            }
        }).start();



    }

    private static void sayHello() {
   
        new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                // 不停输出你好
                while (flag) {
   
                    System.out.println("你好");
                    // 休息一下
                    try {
   
                        // 暂停程序执行3s
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }

                }
            }
        }).start();


    }
}

请添加图片描述

操作系统基本概念

进程(process)与线程(thread)

进程

  • 计算机程序在某个数据集合上的运行活动.进程是操作系统进行资源调度与分配的基本单位
  • 正在运行的程序或者软件

线程

  • 进程中有多个子任务,每个子任务就是一个线程. 从执行路径的角度看, 一条执行路径就是一个线程
  • 线程是CPU进行资源调度与分配的基本单位

进程与线程的关系

  • 线程依赖于进程而存在
  • 一个进程中可以有多个线程(最少1个)
  • 线程共享进程资源
  • 举例: 迅雷

串行(serial),并行(parallel)与并发(concurrency)

串行

  • 一个任务接一个任务按顺序执行

并行

  • 在同一个时间点(时刻)上多个任务同时运行

并发

  • 在同一时间段内,多个任务同时运行

请添加图片描述

同步(synchronization)与异步(asynchronization)

有2个任务(业务) A B

同步:

  • A任务执行的时候B不能执行,按顺序执行
  • 你走我不走

异步:

  • A任务执行的时候,B任务可以执行
  • 你走你的,我走我的,互相不干扰
  • 多线程是天生异步的

假设去书店买本java书, 给老板打了个电话,问一下 有没有java编程思想这本书

同步: 老板接通电话,告诉我去找一下,电话没有挂断, 我在电话另一端等待老板给我回复

异步:老板接通电话,告诉我去找一下,电话挂断了,等到老板找到了,再打电话通知我.

单道批处理: 内存中只能有1个进程在运行

多道批处理: 内存中可以有多个进程在运行 , 进程的上下文切换

现代操作:引入了线程

java程序运行原理

java命令+主类类名运行原理

  • java命令会启动jvm进程, 进程会去创建一个线程 , main线程
  • 执行main线程的main方法

jvm是单线程还是多线程的

结论: 多线程的

至少还有1个线程是垃圾回收线程,负责回收垃圾

单线程的实现方式一:继承Thread类

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程

文档示例请添加图片描述

步骤

  1. 定义一个类继承Thread类
  2. 重写run方法
  3. 创建子类对象
  4. 通过start方法启动线程
package _19thread01.com.cskaoyan._02implone;

/**
 * @description: 多线程的实现方式一
 * @author: 景天
 * @date: 2022/6/27 17:15
 **/

/*
1. 定义一个类继承Thread类
2. 重写run方法
3. 创建子类对象
4. 通过start方法启动线程
 */
public class Demo {
   
    public static void main(String[] args) {
   
        //3. 创建子类对象
        MyThread t = new MyThread();
        //4. 通过start方法启动线程
        t.start();
    }
}

// 1. 定义一个类继承Thread类
class MyThread extends Thread{
   
    // 2. 重写run方法


    @Override
    public void run() {
   
        System.out.println("线程执行了!");
    }
}

注意事项

多线程的执行特点是什么?

执行是随机的

start方法跟run方法有什么区别?

run方法调用,并没有开启新的执行路径,还是单线程在运行, 还是按顺序执行的(相当于普通方法调用)

只要start方法才是开辟了新的执行路径,才会启动线程

使用run方法执行的结果
main start
0
1
2
3
4
5
6
7
8
9
main end   
使用start方法执行的结果
main start
main end
0
1
2
3
4
5
6
7
8
9

同一个线程能否启动多次?

  • 不能启动多次
  • java.lang.IllegalThreadStateException

谁才代表一个线程?

Thread及其子类对象才代表线程

设置获取线程名称

获取名称

String getName() 返回该线程的名称。
默认名称Thread-“线程编号 从0开始”

设置名称

void setName(String name) 改变线程名称,使之与参数 name 相同。
Thread(String name) 分配新的 Thread 对象. 构造方法也可以设置名字

如何获取主线程的名称

static Thread currentThread() 返回对当前正在执行的线程对象的引用。
package _19thread01.com.cskaoyan._02implone;

/**
 * @description: 获取设置线程名称
 * @author: 景天
 * @date: 2022/6/27 17:40
 **/

public class Demo4 {
   
    public static void main(String[] args) {
   
        // currentThread()返回当前正在运行的线程对象的引用
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println("主线程名称:"+ name);


        // 创建子类对象
        MyThread4 t = new MyThread4("王道彭于晏");

        // setName(String name)
        // 设置线程名称
        //t.setName("王道吴彦祖");

        // start启动线程
        t.start();
    }
}

class MyThread4 extends Thread{
   
    public MyThread4(String name) {
   
        super(name);
    }

    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+i);
        }
    }
}

线程的调度方式

什么是线程调度

给线程分配CPU处理权的过程

调度方式的分类

  • 协同式线程调度
    • 线程的执行时间由线程本身觉得,当线程执行完后报告操作系统,切换到别的线程执行
  • 抢占式线程调度方式
    • 线程的执行时间由系统决定,哪个线程抢到,哪个线程执行

java中采用哪种调度方式

java中采用的是抢占式的线程调度方式

线程的优先级(了解)

操作系统优先级

  • 动态优先级
    • 正在运行的线程会随着运行时间的延长,优先级会降低
    • 正在等待的线程会随着等待的时间的延长,优先级升高
  • 静态优先级
    • 固定值

java中优先级

静态优先级(1-10)

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程

static int MAX_PRIORITY 线程可以具有的最高优先级。 10
static int MIN_PRIORITY 线程可以具有的最低优先级。 1
static int NORM_PRIORITY 分配给线程的默认优先级。 5

获取设置线程优先级

int getPriority() 返回线程的优先级。
void setPriority(int n) 设置线程的优先级
package _20thread02.com.cskaoyan._01api;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/28 9:28
 **/

public class PriorityDemo {
   
    public static void main(String[] args) {
   
        // 创建子类对象
        MyThread t = new MyThread();
        // 设置优先级
        t.setPriority(Thread.MAX_PRIORITY);

        // 获取线程的优先级
        int priority = t.getPriority();
        System.out.println(priority);

        // start
        t.start();
    }
}

class MyThread extends Thread{
   
    // run

    @Override
    public void run() {
   
        System.out.println("111111");
    }
}

练习:

创建并启动2个线程 A B

要求2个线程分别打印10个数

A线程为最高优先级 B线程为最低优先级

===> A先打印10个数 然后B再去打印?

package _20thread02.com.cskaoyan._01api;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/28 9:28
 **/

public class PriorityDemo2 {
   
    public static void main(String[] args) {
   
        // 创建子类对象
        MyThread2 t1 = new MyThread2();
        MyThread2 t2 = new MyThread2();
        // 设置优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.setName("A");
        t2.setName("B");

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

class MyThread2 extends Thread{
   
    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+"---"+i);
        }
    }
}

总结

结论: java中的优先级用处不大, 作为了解

然而,我们在java语言中设置的线程优先级,它仅仅只能被看做是一种"建议"(对操作系统的建议),
实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)

java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程
占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点

线程控制API

线程休眠sleep

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
package _20thread02.com.cskaoyan._01api;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/28 9:57
 **/

public class SleepDemo {
   
    public static void main(String[] args) {
   
        
        // 创建子类对象并启动
        new ThreadSleep().start();
    }
}

class ThreadSleep extends Thread{
   
    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(i);
            // 执行sleep 跟TimeUnit 效果一样
            try {
   
                //Thread.sleep(1000);
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}

线程合并join

void join() 等待该线程终止
  • 谁等待?
    • 主线程等待子线程,join这行代码在哪个线程上运行 , 就是哪个线程等待
  • 等待谁?
    • 等待的是子线程, 哪个线程调用了join,等待的就是哪个线程

Demo

package _20thread02.com.cskaoyan._01api;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/28 10:00
 **/

// 线程合并
public class JoinDemo {
   
    public static void main(String[] args) throws InterruptedException {
   
        System.out.println("main start");
        // 创建子类对象
        ThreadJoin t = new ThreadJoin();
        // 启动线程
        t.start();
        t.join();

        // 让main 打印
        for (int i = 0; i <3; i++) {
   
            System.out.println(i);
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }

        System.out.println("main end");

    }
}

class  ThreadJoin extends Thread{
   
    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+"---"+i);
            // 休眠
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}
没有使用join之前的结果
main start
0
Thread-0---0
1
Thread-0---1
2
Thread-0---2
main end
Thread-0---3
Thread-0---4
Thread-0---5
Thread-0---6
Thread-0---7
Thread-0---8
Thread-0---9


调用join之后的结果
main start
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-0---5
Thread-0---6
Thread-0---7
Thread-0---8
Thread-0---9
0
1
2
main end

线程礼让yield

static void yield() 暂停当前正在执行的线程对象,并执行其他线程。

练习:

启动2个线程 A B , 分别打印10个数

要求A打印0, B打印0, A打印1, B打印1…

结论 : 实现不了

        // java中采用抢占式的调度方式  虽然这个线程通过yield放弃了CPU的执行权
        // 但是还能参与下轮的CUP的竞争 下轮谁抢到谁执行
package _20thread02.com.cskaoyan._01api;

/**
 * @description: 线程礼让
 * @author: 景天
 * @date: 2022/6/28 10:14
 **/

/*
启动2个线程 A  B , 分别打印10个数

要求A打印0, B打印0, A打印1, B打印1.....
 */
public class YieldDemo {
   
    public static void main(String[] args) {
   
        // 创建2个线程并启动
        ThreadYield t1 = new ThreadYield();
        ThreadYield t2 = new ThreadYield();

        t1.setName("A");
        t2.setName("B");

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

class ThreadYield extends Thread{
   
    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+"---"+i);
            //立刻执行yield  暂停当前正在执行的线程对象,并执行其他线程。
            Thread.yield();
            // java中采用抢占式的调度方式  虽然这个线程通过yield放弃了CPU的执行权
            // 但是还能参与下轮的CUP的竞争 下轮谁抢到谁执行
        }
    }
}

守护线程setDaemon

线程分类:

  • 用户线程
    • 系统的工作线程(默认的)
  • 守护线程
    • 为系统服务的后台线程(比如垃圾回收线程) , 守护线程就是为用户线程服务, 当做用户线程的"奴仆,佣人"
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
on - 如果为 true,则将该线程标记为守护线程

注意

  • 当正在运行的线程都是守护线程时,Java 虚拟机退出。
  • 该方法必须在启动线程前调用。 (start前) java.lang.IllegalThreadStateException
package _20thread02.com.cskaoyan._01api;

/**
 * @description: 守护线程
 * @author: 景天
 * @date: 2022/6/28 10:51
 **/

public class DaemonDemo {
   
    public static void main(String[] args) {
   
        // 创建子类对象
        ThreadDaemon t = new ThreadDaemon();
        // 设置为守护线程
        t.setDaemon(true);
        // start
        t.start();


        for (int i = 0; i < 3; i++) {
   
            System.out.println(i);
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }

    }
}

class ThreadDaemon extends Thread{
   
    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+"---"+i);
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}

线程中断stop(已过时,了解)

void stop() 已过时。 该方法具有固有的不安全性
package _20thread02.com.cskaoyan._01api;

/**
 * @description: 中断线程
 * @author: 景天
 * @date: 2022/6/28 10:59
 **/

public class StopDemo {
   
    public static void main(String[] args) {
   
        // 创建线程对象并启动
        // 创建子类对象
        ThreadStop t = new ThreadStop();
        // start
        t.start();


        for (int i = 0; i < 3; i++) {
   
            System.out.println(i);
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }

        // stop 强制中断线程
        t.stop();

    }
}

class ThreadStop extends Thread{
   
    // run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            System.out.println(getName()+"---"+i);
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}

安全中断线程

主要通过flag标记去做

练习:

创建启动1个线程 要求打印10个数 每打印1个休眠1s

​ 如果没有中断, 正常打印

​ 如果此时发生了中断, 把中断线程保存到log.txt文件中, 格式 年月日 时分秒 哪个线程发生了中断

主线程打印3个数 每打印1个休眠1s 当打印完之后, 中断子线程 (通过更改flag值)

package _20thread02.com.cskaoyan._01api;

import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @description:
 * @author: 景天
 * @date: 2022/6/28 11:11
 **/

/*
主要通过flag标记去做

练习:

创建启动1个线程   要求打印10个数  每打印1个休眠1s

​	如果没有中断, 正常打印

​	如果此时发生了中断, 把中断线程保存到log.txt文件中, 格式 年月日 时分秒  哪个线程发生了中断

主线程打印3个数  每打印1个休眠1s  当打印完之后, 中断子线程  (通过更改flag值)
 */
public class SecurityStop {
   
    public static void main(String[] args) throws InterruptedException {
   
        // 创建子类对象
        ThreadStop2 t = new ThreadStop2();
        // start
        t.start();
        // main打印
        for (int i = 0; i < 3; i++) {
   
            System.out.println(i);
            Thread.sleep(1000);
        }
        // 中断子线程 更改flag
        t.flag = false;
    }
}

class ThreadStop2 extends Thread{
   
    // 定义成员变量
    boolean flag = true;
    //run

    @Override
    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            // 判断一下flag状态
            if (flag) {
   
                // 如果为true 没有中断 正常打印
                System.out.println(getName() + "---" + i);
                try {
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
            } else {
   

                // 如果为false 发生了中断, 把错误信息保存log.txt文件中
                // 创建输出流对象
                FileWriter fileWriter = null;
                try {
   
                    fileWriter = new FileWriter("log.txt");
                    // 日期
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String date = sdf.format(new Date());

                    // 把要保存的数据写入文件
                    fileWriter.write(date + getName() + "发生了中断!");

                    // flush
                    fileWriter.flush();
                } catch (IOException e) {
   
                    e.printStackTrace();
                }finally {
   
                    // close
                    if (fileWriter != null) {
   
                        try {
   
                            fileWriter.close();
                        } catch (IOException e) {
   
                            e.printStackTrace();
                        }
                    }

                }

            }

        }
    }
}

线程的生命周期

线程的几种状态

理论层面的状态

新建

  • 刚new出来,还没有start

就绪

  • 执行star
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值