[JAVAee]多线程入门介绍及其创建与基础知识

目录

1.进程 

2.线程

3.进程与线程的区别与联系

4.为什么会有线程?

5.创建第一个多线程程序

方法一:继承Theard类

方法二:实现Runnable接口

方法三:匿名内部类创建Thread子类对象

方法四:匿名内部类创建 Runnable 子类对象

方法五(推荐方法):lambda 表达式创建 Runnable 子类对象

体会多线程 

6.Thread类常见的构造方法及其属性

6.1Thread常见构造方法

6.2Thread常见属性

6.3线程的启动方法

6.4线程的休眠方法

6.5线程的中断方法

当interrup方法遇上线程阻塞

 6.6线程的等待方法

6.7当前执行中线程的获取方法

7.线程的状态


1.进程 

要了解多线程,首先要明白进程的概念,简单的来说;

一个已经跑起来的程序就可以称为进程.而使用进程,就是为了能够并发的运行多个任务.

2.线程

一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码

3.进程与线程的区别与联系

  • 进程是系统分配资源的最小单位,而线程是系统调度的最小单位.
  • 进程至少要包含一个线程,这个线程称为主线程.
  • 一个进程中可以有着多个线程.
  • 线程对于进程来说,在创建,销毁和调度方面速度更快,更轻量.
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间
  • 多个程序(即进程)之间是互不影响的,像是QQ与微信.如果你QQ发生了卡顿或者出现了BUG一类事情,并不会影响到微信的工作.
  • 在同一个进程的线程之间,会相互的影响.

4.为什么会有线程?

为了提高程序的运行效率和响应的速度,就有了并发编程的出现.

对于并发编程来说,简单的讲就是可以讲一个任务,分成多个小的子任务去并发的执行.

当然,进程也可以实现并发编程,但线程更轻量的同时也能达到这一目的.所以通常我们使用多线程,即在一个进程中创建多个线程来实现并发编程.

5.创建第一个多线程程序

其实创建一个线程的本质是,创建一个Thread(在Thread类中实现了Runnable接口)实例,在需要启动线程的时候调用实例的start方法,start方法再去调用到Runnable接口中的run方法.

我们在run方法中写入我们想要线程执行的程序,在启动的线程的时候,线程就会相应的执行run方法中的代码.

方法一:继承Theard类

创建一个类MyThread继承父类Thread,并作为其子类.

再重写其父类当中的run方法

class MyThread extends Thread{//创建一个继承Thread的类
    @Override
    public void run() {//并重写其的run方法,在run方法中就是线程要执行的编程
        while(true){
            System.out.println("我是一个小工具人");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();//通过创建好的类,创建一个实例
        thread.start();//调用实例的start方法,来启动一个线程来执行run中的编程
        while(true){
            System.out.println("我是主线程");
        }
    }
}

方法二:实现Runnable接口

创建一个MyRunnable类并实现Runnbale接口

在创建Thread实例的时候,将实例化后的MyRunnable传入Thread构造方法当中,之后该线程就使用MyRunnable类的实例作为运行任务

class MyRunnable implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("我是一个小工具人");
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();//线程开始运行
        while(true){
            System.out.println("我是主线程");
        }
    }
}

方法三:匿名内部类创建Thread子类对象

在创建Thread实例的时候,写一个匿名内部类,这个内部类作为Thread的子类.和方法一类似

public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("我是一个小工具人");
                }
            }
        };
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

方法四:匿名内部类创建 Runnable 子类对象

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("我是一个小工具人");
                }
            }
        });
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

方法五(推荐方法):lambda 表达式创建 Runnable 子类对象

我们线程的本质是Runnable接口中的run方法,Runnable接口本身也是一个函数式接口(一个接口有且只有一个抽象方法即run)

而同时,Thread类又实现了Runnable接口,所以我们可以对Thread使用lambda表达式来以更简洁的方式重写run方法

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while(true){
                System.out.println("我是一个小工具人");
            }
        });
        thread.start();
        while(true){
            System.out.println("我是主线程");
        }
    }

体会多线程 

class MyThread extends Thread{
    @Override
    public void run() {//我们创造出的线程thread执行的
        for(int i = 0; i < 10; i++){
            try {
                Thread.sleep(1000);//每次打印都休眠1s,因为代码执行的速度太快.
                                   //线程调度的切换时间要长一点,
                                   //不休眠在打印台上就体现不出多线程的效果(视觉上)
                                   //别着急,后文有提到sleep
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wow");
        }

    }
}
public class Test {

    public static void main(String[] args) throws InterruptedException {

        MyThread thread = new MyThread();//线程创建的方法随意哈

        thread.start();//创建启动线程thread

        for(int i = 0; i < 10; i++){//主线程main要执行的
            Thread.sleep(1000);
            System.out.println("TT");
        }
    }
}

可以看到,两个线程是并行着执行打印语句的 

6.Thread类常见的构造方法及其属性

6.1Thread常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
Thread thread1 = new Thread();
//参照方法一
Thread thread2 = new Thread(new MyRunnable());
//参照方法二
Thread thread3 = new Thread("ThreadName");
//创建一个实例,并赋予线程名字
Thread thread4 = new Thread(new MyRunnable(),"ThreadName2");
//使用MyRunnable对象创建线程,并赋予线程名字

6.2Thread常见属性

属性 获取方法 说明
ID getId() 获取线程的唯一标识,就像每个人的身份证号,不会重复
名称 getName() 获取线程名
状态 getState() 获取线程目前的状态(线程状态将在后文提及)
优先级 getPriority() 获取线程的优先级,优先级越高越容易被调用
是否后台线程 isDaemon() 查看线程是否为后台线程,在jvm中需要非后台线程全部结束后才结束
是否存活 isAlive() 查看线程是否执行完成run方法中的代码
是否被中断 isInterrupted() 查看线程是否被中断(关于线程中断将在后文提及
public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 5; i++){
                System.out.println("wow!!!");
            }
            System.out.println("OH??!");
        });
        System.out.println(Thread.currentThread().getName() + "-ID: " + Thread.currentThread().getId());
        //获取当前执行的线程名 + 其线程ID
        System.out.println(Thread.currentThread().getName() + "-NAME " + Thread.currentThread().getName());
        //线程名 + 其线程名字
        System.out.println(Thread.currentThread().getName() + "-STATE: " + Thread.currentThread().getState());
        //线程名 + 其线程状态
        System.out.println(Thread.currentThread().getName() + "-PRIORITY: " + Thread.currentThread().getPriority());
        //线程名 + 其线程优先级
        System.out.println(Thread.currentThread().getName() + "-IsDAEMON: " + Thread.currentThread().isDaemon());
        //线程名 + 其是否为后台线程
        System.out.println(Thread.currentThread().getName() + "-IsALIVE: " + Thread.currentThread().isAlive());
        //线程名 + 其是否还在运行
        System.out.println(Thread.currentThread().getName() + "-IsINTERRUPTER: " + Thread.currentThread().isInterrupted());
        //线程名 + 其是否被中断
        thread.start();
        //开启我们创建的线程,没错上面的语句的线程都为主线程哈哈哈
    }

  • 关于后台线程与前台线程,我们创建的线程默认为前台线程.也可以使用setDaemon方法将其更改.
  • 前台线程没有执行完毕之前,java进程是不会关闭的.即java进程要关闭的时候,如果只有后台线程在运行,则会直接关闭,如果前台线程还在运行则会等待前台线程执行完毕之后才会关闭.

6.3线程的启动方法

方法 说明

public void start()

创建并启动一个线程

当我们在想创建一个线程的时候,第一是覆写run方法创建一个Thread的实例.

但是创建Thread的实例并不相当于创建了一个线程,就像是你拿到了一个工具人的电话.

而run方法里的就是这位工具人(线程)接下来要干活的清单.

state方法则是联系工具人(线程)出现,并去执行. 只有调用了state,工具人才会出现,也是这个时候才是真正意义上在底层操作系统上创建出一个线程

6.4线程的休眠方法

这里的休眠是指,线程临时暂停执行,待我们规定的休眠时间一过便会继续执行后面的程序 

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis
毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException
可以更高精度的休眠
public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 10; i++){
                try {
                    Thread.sleep(1000);//休眠的时间为1000ms == 1s
                    System.out.println("working~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

6.5线程的中断方法

一旦真正调用state方法创建并启动线程,除非完成run中的代码.否则线程是不会中途停止的.

方法 说明
public void interrupt() 中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除
public static boolean
interrupted()
判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean
isInterrupted()
判断对象关联的线程的标志位是否设置,调用后不清除标志位
  • 在Thread类内部有一个boolean变量作为线程是否被中断标记.
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

在创建线程thread的三秒后,调用interrupt方法设置其标志位,通知线程中断

在thread线程内使用Thread.interrupted()或Thread.currentThread().isInterrupted()来观察中断标志位,

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!Thread.interrupted()){//thread线程使用interrupted来检测是否要进行中断
                System.out.println("working~");
            }
        });
        thread.start();//thread线程启动
        System.out.println("gogogo");
        Thread.sleep(1000 * 3);//main线程休眠三秒,在main休眠的同时,thread在不断工作
        thread.interrupt();//三秒后thread线程被通知进行中断
    }

当interrup方法遇上线程阻塞

我们来仔细的分析interrupt方法的说明,

中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除

在调用interrupt方法通知线程要进行中断了,设置其中断标志位为true后(仅针对下列代码举例说明):

  1. 如果线程为阻塞状态,则是将线程唤醒,将其线程状态调整为不阻塞状态.清除中断标志位(设置为false)再进入到catch语句当中抛出异常.中断标志位被清除后,thread线程的interrupted方法接收到的中断标志位为false(没有检测到要进行中断)则是会继续执行while语句无限循环.
  2. 如果线程为不阻塞状态,则是由interrupted方法检测到中断标志位的改变,中断thread线程的执行.
  •  阻塞:像是使用wait方法,sleep方法和join方法等其他的来导致线程不去执行下面的代码,或等到一个特定的条件后再去执行
Thread thread = new Thread(() -> {
            while(!Thread.interrupted()){
                System.out.println("working~");
                //结果发现线程为阻塞状态,则唤醒线程(将线程更改为不阻塞)来到catch语句下抛出异常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                //来到这个语句下,抛出异常
                //但其中断标志位,是没有设置的.所以说在抛出异常后线程没有进行中断
                //会继续进行while循环语句
                //但我们可以在catch语句最后加上一个break;使其跳出这个while
                    e.printStackTrace();
                    //break;
                }
            }
        });
        thread.start();
        System.out.println("gogogo");
        Thread.sleep(1000 * 3);//main线程进入sleep休眠(阻塞)3秒
        thread.interrupt();//通知thread线程要中断

 上面的代码结果如下,在进入到catch语句抛出异常.但thread线程的while还在继续,此时main线程已经执行完了,也不会再次调用中断方法.此时的while循环中的表达式可以看成为true了

 当我们在catch语句上加上一个break;可以看到thread在抛出异常后再执行break就可以跳出循环实现我们想要的中断功能.

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
    break;
}

 6.6线程的等待方法

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

使用实例: 

public static void main(String[] args) throws InterruptedException {
        //创建线程实例0
        Thread thread0 = new Thread(() -> {
            for(int i = 0; i < 4; i++){
                System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":唱完了");
        });
        //创建线程实例1
        Thread thread1 = new Thread(() ->{
            try {//在thread1中调用thread0的join.作用为:等待thread0执行完后才执行thread1线程.thread1当前为阻塞状态
                thread0.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {//因为在进程中线程是随机调度的,
                 //所以在线程0结束后,立刻使用到线程1,导致main线程中的话没说完.线程1就开始唱歌
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i = 0; i < 4; i++){
                System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":唱完了");
        });

        System.out.println("游戏开始,规则为谁先站上站台上谁就开始唱歌");
        //启动线程0与线程1开始游戏
        thread0.start();
        thread1.start();
        System.out.println("争夺中~");
        //我们规定此处线程0为第一顺位,实际上进程中的线程调度是完全随机的
        System.out.println("是线程0先抢占到了站台,请线程0开始你的表演");
        thread0.join();
        //在main线程中调用线程0的join方法.main线程等待线程0执行完后才执行下面的语句
        System.out.println("线程0结束了他的表演,请大家为他鼓掌.接下来是线程1为大家表演");
        thread1.join();
        //main线程调用线程1的join方法,main线程等到线程1执行完后才执行下面的语句
        System.out.println("本次游戏结束,谢谢大家");
    }

6.7当前执行中线程的获取方法

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用

  在进程中,线程都是并发的.其调度是具有随机性的.我们并不知道目前正在执行的线程到底是哪一个.我们就可以用此方法来得到目前正在执行的线程.

public static void main(String[] args) {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName());
}

7.线程的状态

状态state 说明
NEW 系统中的线程还没创建(还没start调用),只有一个线程实例
RUNNABLE 线程正在执行,或线程即将开始工作
BLOCKED 线程停止执行,并等待(由线程上锁导致)
WAITING 线程停止执行,并等待(由wait(),join()方法及其他导致)
TIMED_WAITING 线程停止执行,并等待(由wait(long)方法,join(long)方法及sleep()方法及其他导致)
TERMINATED 线程工作结束

猜你喜欢

转载自blog.csdn.net/weixin_67719939/article/details/131809558
今日推荐