多线程相关知识(八股文)

一.相关概念(了解)

1.线程与进程

(1)程序:① 是选择一种编程语言,完成一个功能/任务,而编写的一段代码,这段代码最后被编译/解释为指令。

                    ② 程序是一组指令的集合。

                    ③ 程序是静态的。当我们电脑,手机安装了一个程序之后,只是占用硬盘/存储卡的空间。

(2)进程:① 一个程序的一次运行。

                    ② 当程序启动后,操作系统都会给这个程序分配一个进程的ID,并且会给他分配一块独立的内存空间。

                    ③ 如果一个程序被启动了多次,那么会有多个进程。

                    ④ 多个进程之间是无法共享数据。

                    ⑤ 如果两段代码需要进行数据的交互,成本比较高,要么通过一个硬盘的文件,要么通过网络。

                     ⑥ 进程之间的切换成本也比较高。现在的操作系统都支持多任务,操作系统在每个任务之间进行切换,要给整个进程做镜像,要记录当前进程的状态,执行到那个指令的。

                     ⑦ 操作系统分配资源的最小单位是进程。

                     ⑧ 每一个进程至少有一个线程。

(3)线程:① 线程是进程中的其中一条执行路径。多个线程会同属于一个进程。

                    ② 这多个线程会共享同一个进程中的一些资源。比如java中堆内存的数据,方法区的数据。        

                    ③ 如果同一个进程的两段代码需要进行数据的交互,非常方便,可以直接在内存中共享。当然,同时要考虑安全问题。

                    ④ 线程之间的切换,需要记录的信息要少很多,因为很多线程之间的数据是共享的,这些数据就不用单独在做镜像了,只需要记录每一个线程要执行的下一条指令。

                    ⑤ CPU调度的最小单位是线程。

Java程序中一个main方法的程序,就是一个进程。但是可以在main中,再开启多个线程。

Java程序后台有几个线程是默默运行的:GC线程,异常的检查和处理线程,类加载器线程。

2.并发和并行

并行:指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。(并行只能在多核CPU上才能运行)

例子:多项工作一起执行,之后再汇总,如:泡方便面,电水壶烧水,一边撕调料倒入桶中

并发:指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

例子:同一时刻多个线程在访问同一个资源,多个线程对一个点,如:春运抢票

单核CPU:只能并发

多核CPU:并发+并行

3.线程调度(两种)

  • 分时调度
    • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
  • 抢占式调度
    • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。

4.创建和启动线程多种方式

一个进程至少要有一个线程,Java程序至少有一个main线程,是主线程。

Java中开启另一个线程的方式,一共有四种:

① 继承Thread类

② 实现Runnable接口

③ 实现Callable接口

④ 线程池

第一种继承Thread类

步骤:

① 编写一个类,让他继承Thread类

② 重写父类的public void run(){}

这个run()方法不是由程序员调用的,而是线程调度时,自动调用。

要让一个线程做什么事,必须把这个代码写到run方法中。

把run()方法的方法体,称为线程体。

③ 创建自定义线程类的对象

④ 启动线程,调用自定义线程类的对象的start()。

 代码如下:

public class TestCreateThread {
    public static void main(String[] args) {
        MyThread my = new MyThread();
        my.start();//这个start方法是从Thread类继承
//        my.run();//不要手动调用run方法,如果手动调用这个方法,就不是线程了
 
/*        new Thread(){//Thread创建线程的方式二
            @Override
            public void run() {
                //让这个线程打印[1-10]的偶数
                for(int i=2; i<=10; i+=2){
                    System.out.println("自定义线程: " + i);
                }
            }
        }.start();*/
/*
        Thread t =  new Thread(){//Thread创建线程的方式三
            @Override
            public void run() {
                //让这个线程打印[1-10]的偶数
                for(int i=2; i<=10; i+=2){
                    System.out.println("自定义线程: " + i);
                }
            }
        };
        t.start();*/
 
        //在main线程中,打印[1-10]的奇数
        for(int i=1; i<=10; i+=2){
            System.out.println("main:" + i);
        }
    }
}
class MyThread extends Thread{//Thread创建线程的方式一
    @Override
    public void run() {
        //让这个线程打印[1-10]的偶数
        for(int i=2; i<=10; i+=2){
            System.out.println("自定义线程: " + i);
        }
    }
}

第二种实现Runnable接口

步骤:

① 编写线程类,实现Runnable接口

② 重写接口的抽象方法public void run()

③ 创建自定义线程类的对象

④ 创建一个Thread类的对象,同时让Thread对象代理我们的自定义线程对象,创建它的目的是为了调用start方法

⑤ 启动线程

线程调度器会调用 t 对象的run方法,因为这里启动的是 t 线程。(t.start())

Thread类的run()

        @Override

        public void run(){

                if(target  !=  null){

                        target.run();

                 }

         }

这里的 target 对象就是创建 Thread 类对象时传入的 Runnable 接口的实现类对象,即被代理对象。

当 my 实参给 target 赋值后,target 就不会为 null,就会执行 my 对象的 run 方法

代码如下:

public class TestCreateThread2 {
    public static void main(String[] args) {
        MyRunnable my = new MyRunnable();
        Thread t = new Thread(my);//让t对象代理my对象
        t.start();
 
/*        new Thread( new Runnable(){//Runnable创建线程的方式二
            @Override
            public void run() {
                //让这个线程打印[1-10]的偶数
                for(int i=2; i<=10; i+=2){
                    System.out.println("自定义线程: " + i);
                }
            }
        }).start();*/
 
/*        Thread t = new Thread(new Runnable(){//Runnable创建线程的方式三
            @Override
            public void run() {
                //让这个线程打印[1-10]的偶数
                for(int i=2; i<=10; i+=2){
                    System.out.println("自定义线程: " + i);
                }
            }
        });
        t.start();*/
 
 
        //在main线程中,打印[1-10]的奇数
        for(int i=1; i<=10; i+=2){
            System.out.println("main:" + i);
        }
    }
}
 
class MyRunnable implements Runnable{//Runnable创建线程的方式一
    @Override
    public void run() {
        //让这个线程打印[1-10]的偶数
        for(int i=2; i<=10; i+=2){
            System.out.println("自定义线程: " + i);
        }
    }
}

java.lang.Thread类有哪些方法?

系列方法一:构造方法

① public Thread():分配一个新的线程对象。

子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造

class SubThread extends Thread{
    //子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
 
    public SubThread() {//默认调用无参构造器
        super();
    }
}

② public Thread(String name):分配一个指定名字的新的线程对象。

public class ThreadMethod1 {
    public static void main(String[] args) {
        SubThread s2 = new SubThread("线程2");
        s2.start();
}
class SubThread extends Thread{
    public SubThread(String name) {
        super(name);
    }
}

③ public Thread(Runnable target):分配一个带有指定目标新的线程对象

public class ThreadMethod1 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
}
class SubThread extends Thread{
    @Override
    public void run() {
        System.out.println(getName());//getName()方法从父类Thread继承的
    }
}

④ public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

public class ThreadMethod1 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },"线程5").start();
    }
}
class SubThread extends Thread{
    @Override
    public void run() {
        System.out.println(getName());//getName()方法从父类Thread继承的
    }
}

String getName():获取线程的名称

        如果没有手动指定线程名称,默认是Thread-编号,从0开始

        如果需要手动指定线程名称,可以通过构造器,或者setName(String name)方法设置线程名称。

class SubThread extends Thread{
    //子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
    public SubThread() {
        super();
    }
 
    public SubThread(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        System.out.println(getName());//getName()方法从父类Thread继承的,获取线程名字
    }
}

static Thread currentThread():获取执行当前语句的线程对象。

public class ThreadMethod1 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //Thread.currentThread().getName()获取线程名字
                System.out.println(Thread.currentThread().getName());
            }
        },"线程5").start();
    }
}

整合代码如下:

public class ThreadMethod1 {
    public static void main(String[] args) {
        SubThread s1 = new SubThread();
        SubThread s2 = new SubThread("线程2");
        SubThread s3 = new SubThread();
        s1.start();
        s2.start();
        s3.start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },"线程5").start();
    }
}
class SubThread extends Thread{
    //子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
 
    public SubThread() {
        super();
    }
 
    public SubThread(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        System.out.println(getName());//getName()方法从父类Thread继承的
    }
}

子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造

线程优先级问题

方法:

① public final int getPriority():返回线程优先级

② public final void setPriority(int newPriority):改变线程的优先级

线程优先级高的,有更多的机会/概率被优先调用。

线程优先级范围:[MIN_PRIORITY,MAX_PRIORITY],即[1,10]

3个常量值:

MIN_PRIORITY:1

MAX_PRIORITY:10

NORM_PRIORITY:5

代码如下:

public class ThreadMethod2 {
    public static void main(String[] args) {
//        System.out.println(Thread.MAX_PRIORITY);
//        System.out.println(Thread.MIN_PRIORITY);
 
        Thread t = new Thread(){
            public void run(){
                /*for(int i=2; i<=10; i+=2){
                    System.out.println("自定义线程:" + i);
                }*/
                System.out.println(getName() +":" + getPriority());//得到线程优先级
            }
        };
      //  t.setPriority(100);//java.lang.IllegalArgumentException 非法参数异常
                        //当设置的优先级不在 MIN_PRIORITY 到 MAX_PRIORITY 范围内,就会报这个异常。
//        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
 
       /* for(int i=1; i<=10; i+=2){
            System.out.println("主线程:" + i);
        }*/
        System.out.println(Thread.currentThread().getName() +":" + Thread.currentThread().getPriority());
    }
}

与线程的状态有关的方法:

public static void sleep(long millis) throws InterruptedException:线程休眠,单位毫秒

public static void yield():让当前线程暂停一下。当前线程暂停下,让出CPU,但是下一次CPU有可能还是调用它。

void join() throws InterruptedException:等到该线程终止。该线程是调用join方法的线程。

void join(long millis):等待该线程终止的时间最长为 millis 毫秒。如果 millis 时间到,将不再等待。

void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

public class ThreadMethod3 {
    public static void main(String[] args) {
        /*for(int i=10; i>=1; i--){
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("新年到!");*/
 
        Thread t = new Thread("偶数线程"){
            @Override
            public void run(){
                for(int i=2; i<=20; i+=2){
                    System.out.println(getName() +":" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
 
        for(int i=1; i<=100; i+=2) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            if(i==5) {
                //让奇数线程暂停一下
//                Thread.yield();
                //让奇数线程停止,彻底让出CPU,让偶数线程执行完再继续
                /*try {
                    t.join();//偶数线程加塞,阻塞了奇数线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                try {
                    t.join(5000);//偶数线程加塞5秒,阻塞了奇数线程5秒,5秒之后又抢CPU
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程的扩展练习:加塞

案例:自定义线程类ChatThread:问是否结束(输入Y/y结束),如果输入的不是y,继续问是否结束,直到输入y才结束。

主线程打印[1,10],每隔10毫秒打印一个数字,现在当主线程打印完5之后,

就让自定义线程类加塞,直到自定义线程类结束,主线程再继续。

class ChatThread extends Thread{
    public void run(){
        //从键盘输入内容
        Scanner input = new Scanner(System.in);
        while(true){
            System.out.print("是否结束(输入Y/y结束)?");
            //charAt是将输入内容转换为字符串
            /*char confirm = input.next().charAt(0);
 
            if(confirm == 'Y' || confirm == 'y'){
                break;
            }*/
            String answer = input.nextLine();
            //因为String是引用数据类型,所以不能用 == 进行比较,== 比较的是地址,所以要用equals
            //charAt是转化为字符串,Character.toUpperCase将小写字母转换为大写字母
            if(!answer.equals("") && Character.toUpperCase(answer.charAt(0))== 'Y' ){
                break;
            }
        }
        input.close();
    }
}
public class Exercise2 {
    public static void main(String[] args) {
        for(int i=1; i<=10; i++){
            System.out.println(i);
            if(i==5){
                ChatThread t = new ChatThread();
                t.start();
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程的扩展练习:修改和加塞(龟兔赛跑

案例:编写龟兔赛跑多线程程序,设赛跑长度为30米 ==>从1循环到30

兔子的速度是10米每秒(跑1米100毫秒sleep(100)),兔子每跑完10米休眠的时间10秒

乌龟的速度是1米每秒(跑1米1秒sleep(100)),乌龟每跑完10米的休眠时间是1秒

要求:要等兔子和乌龟的线程结束,主线程(裁判)才能公布最后的结果。

提示:System.currentTimeMillis()方法可以返回当前时间的毫秒值(long类型)

long start = System.currentTimeMillis();

中间代码

long end = System.currentTimeMillis();

System.out.println("中间代码运行耗时:" + (end - start) + "毫秒");

Tu代码:

public class Tu extends Thread {
    private long time;
    //给线程设置名字
    public Tu(String name) {
        super(name);
    }
 
    public void run(){
        long start = System.currentTimeMillis();
        for(int i=1; i<=30; i++){
            try {
                Thread.sleep(100);//休眠100毫秒,模拟每打印1个数字,代表跑了1米,花了100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() +"跑了" + i +"米");
 
            if(i==10 || i==20){
                try {
                    Thread.sleep(10000);//休息10秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        time = end-start;
        System.out.println(getName()+"耗时:" + time);
    }
 
    public long getTime() {
        return time;
    }
}

Gui代码:

public class Gui extends Thread {
    private long time;
    //给线程设置名字
    public Gui(String name) {
        super(name);
    }
 
    public void run(){
        long start = System.currentTimeMillis();
        for(int i=1; i<=30; i++){
            try {
                Thread.sleep(1000);//休眠1秒,模拟每打印1个数字,代表跑了1米,花了1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() +"跑了" + i +"米");
 
            if(i==10 || i==20){
                try {
                    Thread.sleep(1000);//休息1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        time = end-start;
        System.out.println(getName()+"耗时:" + time);
    }
    public long getTime() {
        return time;
    }
}

执行代码:

public class Exercise3 {
    public static void main(String[] args) {
        Gui g = new Gui("乌龟");
        g.start();
 
        Tu t = new Tu("兔子");
        t.start();
 
        try {
            g.join();//让乌龟这个线程执行完,才执行if方法,否则会直接输出 平局
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t.join();//让兔子这个线程执行完,才执行if方法,否则会直接输出 平局
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        if(g.getTime() < t.getTime()){
            System.out.println("乌龟赢");
        }else if(g.getTime() > t.getTime()){
            System.out.println("兔子赢");
        }else{
            System.out.println("平局");
        }
    }
}

如何让线程提前结束?

一个线程如何让另一个线程提前结束呢?

线程的死亡有两种:

自然死亡:当一个线程的 run 方法执行完,线程自然会停止

意外死亡:当一个线程遇到未捕获处理的异常,也会挂掉。

  • public final void stop():强迫线程停止执行。该方法具有不安全行,已过时废弃
  • 标记法

案例:声明一个PrintEvenThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的偶数,要求每隔1毫秒打印1个偶数。

声明一个PrintOddThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的奇数。

在main线程中:

(1)创建两个线程对象,并启动两个线程

(2)当打印奇数的线程结束了,让偶数的线程也停下来,就算偶数线程没有全部打印完[1,100]之间的偶数。

public class ThreadMethod4 {
    public static void main(String[] args) {
        PrintEvenThread p1 = new PrintEvenThread();
        PrintOddThread p2 = new PrintOddThread();
        p1.start();
        p2.start();
 
        try {
            p2.join();//等待p2(打印奇数)线程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        p1.setFlag(false);
    }
}
class PrintEvenThread extends Thread{
    private boolean flag = true;
 
    public void run(){
        for(int i=2; i<=100 && flag; i+=2){
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
class PrintOddThread extends Thread{
    public void run(){
        for(int i=1; i<=100; i+=2){
            System.out.println(i);
        }
    }
}

龟兔赛跑代码改进:

增加一个Sporter类代码:(将Gui和Tu的公共代码复用)

public class Sporter extends Thread {
    private long time;
    private long runtimePerMeter;//跑1米的时间
    private long restTime;//休息时间
    private static boolean flag = true;//所有Sporter对象共享
    private final int MAX_DISTANCE = 30;//最多跑30米
    private int distance;//已经跑了几米
 
    public Sporter(String name, long runtimePerMeter, long restTime) {
        super(name);
        this.runtimePerMeter = runtimePerMeter;
        this.restTime = restTime;
    }
 
    public void run(){
        long start = System.currentTimeMillis();
        while(distance < MAX_DISTANCE && flag){
            try {
                Thread.sleep(runtimePerMeter);
                distance++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() +"跑了" + distance +"米");
 
            if(distance==10 || distance==20){
                try {
                    Thread.sleep(restTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if(distance == MAX_DISTANCE){
            System.out.println(getName()+"已经到达终点");
            flag = false;
        }
 
        long end = System.currentTimeMillis();
        time = end-start;
        System.out.println(getName()+"耗时:" + time);
    }
    public long getTime() {
        return time;
    }
 
    public int getDistance() {
        return distance;
    }
}

执行代码:

public class Exercise4 {
    public static void main(String[] args) {
        Sporter gui = new Sporter("乌龟",1000,1000);
        Sporter tu = new Sporter("兔子",100,10000);
        gui.start();
        tu.start();
 
        try {
            gui.join();//这里阻塞的是main线程,和tu(兔子)线程无关。
            /*
            gui.join();这句代码是 main线程执行的,哪个线程执行,哪个线程就被gui线程给阻塞了。
            此时gui和tu线程是并列的关系,还在竞争CPU资源,继续运行。
            (1)情况一
            如果此时tu(兔子)线程先结束了,main线程还要等gui它,结束才能执行下面的代码。等待gui线程结束,main才往下走。
            tu.join(); 因为tu已经结束了,相当于就无法阻塞main线程了,就继续往下走。
            (2)情况二
            如果此时gui(乌龟)线程先结束了,main线程继续往下走,
            tu.join();这句话,因为此时tu没结束,那么main就会被tu加塞了,等到tu线程结束,main才往下走。
 
           (3)结论
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            tu.join();//这里阻塞的是main线程,和gui(乌龟)线程无关。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        //*****(3)结论上面两个线程都结束才会执行下面的代码
        if(tu.getDistance() > gui.getDistance()){
            System.out.println("兔子赢");
        }else if(tu.getDistance() < gui.getDistance()){
            System.out.println("乌龟赢");
        }else{
//            System.out.println("平局");
//            在严格一点,如果都到达终点,看时间
            if(gui.getTime() < tu.getTime()){
                System.out.println("乌龟赢");
            }else if(gui.getTime() > tu.getTime()){
                System.out.println("兔子赢");
            }else{
                System.out.println("平局");
            }
        }
    }
}

你可能感兴趣的:(html,前端)