Java教程

JavaseLearn18-多线程

本文主要是介绍JavaseLearn18-多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JavaseLearn18-多线程

1.多线程概述

1.1 什么是进程?什么是线程?

  • 进程是一个应用程序
  • 线程是一个进程中的执行场景/执行单元
  • 一个进程可以启动多个线程

对于一个java程序来说,当开始执行后,会先启动JVM,而JVM就是一个进程。

JVM再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。故一个java程序起码有两个线程并发。

1.2进程与线程的关系

举个栗子:

  • 美团:进程
    • 王兴:线程
    • 外卖小哥:线程
  • 阿里巴巴:进程
    • 马云:线程
    • 保安:线程

进程可看作是一个公司。

线程可看作是公司里的一名员工。

注意:

  • 进程A与进程B之间内存独立不共享(如美团和阿里巴巴资源是不共享的)。
  • 一个线程一个栈。
  • 线程A与线程B之间,堆内存和方法区内存共享,但是栈内存互相独立。

1.3多线程并发

假设启动10个线程,就会开辟10个栈空间,每个栈之间互不干扰,各自执行各自的,这就是多线程并发。

1.3.1举个栗子:

学校食堂可以看作是一个进程,

食堂里每一个窗口可以看作是一个线程,

我在A窗口打饭,你可以在B窗口打饭,你不需要等我,我也不需要等你。

故多线程并发可以提高效率。

Java中之所以有多线程机制,目的就是为了提高程序的处理效率。

注意:

使用了多线程机制后,main方法结束,有可能程序也不会结束。

main方法结束只代表主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。

1.3.2思考问题:

对于单核的cpu来说,可以做到真正多线程并发吗?

什么是真正的多线程并发?

t1线程执行t1的,t2执行t2的,t1不会影响到t2,t2也不会影响t1。

这就是真正的多线程并发。

对于单核的cpu来说只有一个大脑,无法做到真正的多线程并发,但可以给人一种"多线程并发的感觉"。

在某一个时间点上,单核的cpu只能执行一个线程,但是由于cpu执行速度极快,通过频繁切换不同的线程,给人一种同时处理多个线程的感觉。

就好比你在玩炉石,同时后台用网易云播放着"蜜雪冰城甜蜜蜜"。

线程A:炉石

线程B:网易云"蜜雪冰城甜蜜蜜"

线程A和线程B频繁切换运行,你就感觉游戏一直在运行,音乐一直在播放。

1.5分析程序存在几个线程

以下程序中有几个线程?

/**
 * @Author: TSCCG
 * @Date: 2021/06/18 17:41
 */
public class ThreadDemo01 {
    public static void main(String[] args) {
        System.out.println("main begin");
        t1();
        System.out.println("main over");
    }

    private static void t1() {
        System.out.println("t1 begin");
        t2();
        System.out.println("t1 over");
    }

    private static void t2() {
        System.out.println("t2 begin");
        t3();
        System.out.println("t2 over");
    }

    private static void t3() {
        System.out.println("t3执行!");
    }
}

答:除垃圾回收外,只有一个main主线程,主栈

理由:没有启动分支栈,没有启动分支线程,故只有一个主线程,一个栈

结果:

main begin
t1 begin
t2 begin
t3执行!
t2 over
t1 over
main over

程序线程数

2.实现线程的方式

若要开启一条新的线程,必须要用到start()方法和run方法

start方法:

  • 启动一个分支线程,在JVM中开辟一块新的栈空间,启动完后立即结束运行start方法

run()方法:

  • run()方法需要在分支栈里重写,地位相当于主栈中的main()方法。
  • 启动成功的线程会自动调用run方法,run方法在分支栈的最底部(压栈)。
  • main方法在主栈的最底部,run方法在分支栈的最底部,两者是平级的

2.1第一种_

实现线程的第一种方法:

写一个继承Thread类的类,重写run方法。

使用时创建一个线程对象,然后调用start()方法。

/**
 * @Author: TSCCG
 * @Date: 2021/06/18 20:02
 */
public class ThreadDemo02 {
    //main方法,属于主线程,运行在主栈中
    public static void main(String[] args) {
        //创建一个分支线程对象
        MyThread01 myThread = new MyThread01();
        //启动线程,启动完后立即结束start方法
        myThread.start();
        //此处如果没有调用start方法,直接调用run方法,程序仍然只有一个线程
        //因为没有开启新的线程,相当于是调用了一个普通方法
        //myThread.run();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程---->" + i);
        }
    }
}
 class MyThread01 extends Thread{
    /**
     * 此处重写的run方法相当于主线程里的main
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程---->" + i);
        }
    }
}

结果:

主线程---->0
分支线程---->0
分支线程---->1
分支线程---->2
主线程---->1
分支线程---->3
分支线程---->4
主线程---->2
分支线程---->5
主线程---->3
主线程---->4
分支线程---->6
分支线程---->7
分支线程---->8
分支线程---->9
主线程---->5
主线程---->6
主线程---->7
主线程---->8
主线程---->9

2.2第二种T_T

创建线程的第二种方法:

写一个实现Runnable接口的类,叫可执行类

使用时将该类的实例对象作为参数传入Thread类里,转换成线程对象,然后通过该线程对象调用start()方法

/**
 * @Author: TSCCG
 * @Date: 2021/06/19 09:29
 */
public class ThreadDemo03 {
    public static void main(String[] args) {
        //创建一个可运行类
        MyRunnable r = new MyRunnable();
        //将可运行类的对象作为参数传入Thread类的构造方法里,转换成线程对象
        Thread t = new Thread(r);//也可以这样写:Thread t2 = new Thread(new MyRunnable());
        //开辟一个分支栈,开启分支线程
        t3.start();
    }
}
 class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程---->" + i);
        }
    }
}

也可以采用匿名内部类方式

Thread t3 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程---->" + i);
        }
    }
});
t3.start();

3.线程生命周期

一个线程的周期一共分为五个状态:

  1. 新建状态----线程对象刚被new出来

  2. 就绪状态----调用start方法后,开辟了栈空间,可以抢夺CPU时间片(执行权)

  3. 运行状态----抢夺到CPU时间片后的线程,开始执行各自的程序,主线程执行main方法,分支线程执行run方法。当执行完时间片的时间后,会重新回到就绪状态

  4. 阻塞状态----当遇到sleep、Scanner等事件时,线程就会进入阻塞状态,放弃占有的CPU时间片,当结束阻塞状态时,再次回到就绪状态

  5. 死亡状态----当线程的main或run方法执行完后,就会进入死亡状态

import java.util.Scanner;

/**
 * @Author: TSCCG
 * @Date: 2021/06/19 09:29
 */
public class ThreadDemo03 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //创建线程对象
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("分支线程---->" + i);
                }
            }
        });
        //开启分支线程
        t3.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程---->" + i);
            //放置一个阻塞事件,当i == 5时,主线程阻塞,分支线程继续执行,
            //当输入一个整数时,阻塞事件结束,主线程继续运行
            if(i == 5){
                System.out.println("遭遇阻塞事件");
                int j = sc.nextInt();
                System.out.println("阻塞事件结束");
            }
        }

    }
}

结果:

主线程---->0
主线程---->1
主线程---->2
主线程---->3
主线程---->4
分支线程---->0
分支线程---->1
分支线程---->2
分支线程---->3
分支线程---->4
分支线程---->5
主线程---->5
遭遇阻塞事件
分支线程---->6
分支线程---->7
分支线程---->8
分支线程---->9
3
阻塞事件结束
主线程---->6
主线程---->7
主线程---->8
主线程---->9

4.线程的常用方法

4.1获取当前线程对象

静态方法,通过Thread来调用

Thread t = Thread.currentThread();

4.2获取当前线程名字

String name = 线程对象.getName();

默认情况下,线程的名字默认为:

Thread-0

Thread-1

Thread-2

4.3修改当前线程名字

线程对象.setName("线程名字");

案例:

/**
 * @Author: TSCCG
 * @Date: 2021/06/19 10:27
 */
public class ThreadDemo04 {
    public static void main(String[] args) {
        //创建线程对象t1
        MineThread t1 = new MineThread();
        //设置线程t1的名字
        t1.setName("t1");
        //开启线程t1
        t1.start();

        //创建线程对象t2
        MineThread t2 = new MineThread();
        t2.setName("t2");
        t2.start();

        for (int i = 0; i < 10; i++) {
            //获取当前线程对象
            Thread t = Thread.currentThread();
            //获取当前线程对象
            System.out.println(t.getName() + "---->" + i);
        }
    }
}
class MineThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //获取当前线程对象
            Thread t = Thread.currentThread();
            //获取当前线程对象
            System.out.println(t.getName() + "---->" + i);
        }
    }
}

结果:

main---->0
main---->1
main---->2
main---->3
t2---->0
t1---->0
t1---->1
t2---->1
t2---->2
main---->4
t2---->3
t1---->2
t2---->4
main---->5
t2---->5
t1---->3
t2---->6
main---->6
t2---->7
t1---->4
t2---->8
t2---->9
main---->7
t1---->5
t1---->6
main---->8
t1---->7
main---->9
t1---->8
t1---->9

4.4 sleep()方法

4.4.1 作用及用法

static void sleep(long millis)

1.静态方法:Thread.sleep(1000);

2.单位是1毫秒

3.作用:让当前线程进入休眠,进入"阻塞状态",放弃占有CUP时间片,让给其他线程使用

注意:

Thread.sleep(1000);出现在哪个线程,哪个线程就会进入休眠

例:

/**
 * @Author: TSCCG
 * @Date: 2021/06/19 10:55
 */
public class ThreadDemo05 {
    public static void main(String[] args) {
        System.out.println("让子弹飞一会儿");
        try {
            Thread.sleep(1000 * 5);//让主线程休眠5s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);//每次休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("砰!---->" + i);
        }
    }
}

结果:

让子弹飞一会儿
砰!---->0
砰!---->1
砰!---->2
砰!---->3
砰!---->4
砰!---->5
砰!---->6
砰!---->7
砰!---->8
砰!---->9

4.4.2 sleep()面试题

判断以下程序中t1线程是否会休眠2s?

/**
 * @Author: TSCCG
 * @Date: 2021/06/27 15:43
 * sleep()面试题
 */
public class ThreadDemo06 {
    public static void main(String[] args) {

        Thread t1 = new MineThread02();
        t1.start();
        t1.setName("t1");
        try {
            //这里使用线程对象t1调用sleep()方法
            t1.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HelloWorld!");

    }
}
class MineThread02 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + "---->" + i);
        }
    }

}

结果:

t1---->0
t1---->1
t1---->2
t1---->3
t1---->4
t1---->5
t1---->6
t1---->7
t1---->8
t1---->9
HelloWorld!

从结果可见,通过线程对象t1调用sleep()方法后,t1线程并没有休眠,而是主线程休眠2s。

为什么呢?

这是因为sleep()是静态方法,不应该通过类对象来调用,只能通过类'Thread'直接访问。

t1.sleep(2000);这句代码实际执行的是Thread.sleep(2000);

4.4.3 中途唤醒线程

通过调用interrupt()方法可以中断程序的休眠

/**
 * @Author: TSCCG
 * @Date: 2021/06/27 11:23
 */
public class ThreadDemo07 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable02());
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在主线程休眠5s后,将t线程唤醒
        t.interrupt();
    }
}
class MyRunnable02 implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "----> begin");
        //在run()方法中,只能通过try...catch处理异常,而不能用throws将异常抛出
        //因为run()方法在父类中没有抛出任何异常,而子类不能抛出更多的异常
        try {
            //使分支线程休眠1年
            Thread.sleep(1000 * 60 * 60 * 24 * 365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "----> end");

    }
}

结果:

在打印"t----> begin" 五秒后打印线程休眠中断异常信息,然后打印"t----> end"

t----> begin
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at MyRunnable02.run(ThreadDemo07.java:28)
	at java.lang.Thread.run(Thread.java:745)
t----> end
这篇关于JavaseLearn18-多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!