梳理java多线程

本文详细介绍了Java中的多线程概念,包括线程调度、并发与并行的区别、多线程的实现方式、线程同步与异步、线程池的使用,以及Callable接口用于带返回值的线程。通过实例代码展示了synchronized和Lock的使用,解释了它们之间的区别,并讨论了线程的六种状态。最后,探讨了线程池ExecutorService的概念和好处,以及Java中四种线程池的使用场景。
摘要由CSDN通过智能技术生成

线程与进程

进程:

是指一个内存中运行的应用程序,每个进程有一个独立的内存空间

线程:

进程中的执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。

线程实际上是进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

每个线程都有自己的栈空间,共用一份堆内存。

线程调度

分时调度

所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间

抢占式调度

优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

抢占式调度

cpu使用抢占式调度模式在多个线程间进行高速切换,看起来像是同时做多件事情。多线程并不能提高程序的运行速度,但能提高运行的效率。

并发与并行

并发:可以是一个处理器或者是多个处理器,通过来回切换运行,使得看起来像是同时发生,实际上是两个或多个事件在同一个时间段发生。

并行:一个处理器以上,两个或多个事情在同一个时刻发生(同时发生)

多线程的实现

继承Thread,继承Thread的类则须重写其run方法,run方法的调用则是执行了一条新的路径,这个执行路径的触发方式,不是调用run,而是通过thread对象的start()方法来启动任务。

public class MyThread extends Thread{
​
    /**
     * run方法是线程要执行任务的方法
     */
    public void run() {
        //这里的代码就是一条新的执行路径
        //这个执行路径的触发方式,不是调用run,而是通过thread对象的start()方法来启动任务
        for (int i=0;i<10;i++){
            System.out.println("一条大河波浪宽"+i);
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //一开始main函数就开启了一个线程
        MyThread m = new MyThread();
        m.start();//这个方法开启了第二个线程
        for (int i=0;i<10;i++){
            System.out.println("风吹稻花上两岸"+i);
        }
    }
}
//还可以使用匿名内部类的方式实现多线程
public class Demo1 {
    new Thread(){
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                System.out.println("一条大河波浪宽"+i);
            }
        }
    }.start();
    for (int i=0;i<10;i++){
        System.out.println("风吹稻花上两岸"+i);
    }
}

实现接口Runnable

创建一个实现Runnable接口的类,创建它的一个对象(任务),new一个Thread对象把任务传进去。然后执行这个Thread的star方法。

public class MyRunnable implements Runnable{
​
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println("床前明月光"+i);
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();    //视作创建了一个任务
        Thread t = new Thread(r);   //交给多个线程去执行
        t.start();
        for (int i=0;i<10;i++){
            System.out.println("疑是地上霜"+i);
        }
    }
}

实现runnable与继承Thread相比有如下优势:

1、通过创建任务(也就是实现Runnable的类创建出来的对象),然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。

2、可以避免单继承带来的局限性。(可以同时实现多个接口,但是只能继承一个父类)

3、任务与线程本身是分离的,提高了程序的健壮性。

4、后续学习的线程池技术,接受runnable类型的任务,不接受thread。

给线程设置名称

如果是Runnable则有两个方法

public static void main(String[] args) {
        new Thread(new MyRunnable(),"大河线程");
        //一种
        Thread t = new Thread(new MyRunnable());
        t.setName("这是我父亲日记里的文字。");
        //另一种
        t.start();
}
    static class MyRunnable implements Runnable{
​
        public void run(){
            System.out.println(Thread.currentThread().getName());
        }
    }

而若没有使用Runnable直接是继承Thread类的对象则是只有后一种方法,Thread对象.setName()方法。

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("一条大河");
        myThread.start();
    }
    private static class MyThread extends Thread{
​
        public void run(){
            System.out.println(Thread.currentThread().getName());
        }
    }

线程休眠

可以通过Thread.sleep(1000);括号里是毫秒,来让线程休眠1000毫秒后再执行。

线程阻塞

所有消耗时间的操作,比如读文件,或接受用户输入,都能算是线程阻塞,意味着现在线程处于暂停状态也没能去做其他事。

线程中断

线程中断的操作是给Thread线程对象打上标记(异常),然后在线程的run方法中设置catch来捕获这个异常,在catch代码块中用return可使得直接跳出这个run方法,结束这个线程,而也可以设置选择场景在catch中决定是否要中断(不中断则不做任何操作),实际的应用中,如果要中断也要在return前释放各种资源。

package com.test4_5;
//线程的中断
//一个线程是个独立的执行路径,是否结束应该由自身决定。外部掐死总是一种能导致各种无可预料的错误,
//而如果有需要中断,应该给线程打标记,线程特殊情况下会查看标记,如果有则会触发一个异常
//程序员可以设置try catch 来决定如何结束这个线程(或不结束)。
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new MyRunnable());
        t1.start();
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //给线程t1添加中断标记,设置的catch中就能依照程序员的设计选择是否让它死亡。
        t1.interrupt();
    }
    private static class MyRunnable implements Runnable{
        //下面的sleep可能会有异常,而在这个类中无法抛出异常,因为实现了Runnable接口,Runnable父接口
        //都没有声明异常的抛出,子就不能抛出比父更大的异常。所以我们选择try catch
​
        @Override
        public void run() {
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    //System.out.println("发现了终端标志,如果不想让它死亡就可以什么也不做。");
                    System.out.println("发现了终端标志,选择线程死亡则执行return。");
                    return;
                }
            }
        }
​
    }
}

用户线程和守护线程

当一个进程不包含任何存活的用户线程时,进程结束。

1、什么是守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) ,直接创建的线程都是用户线程。

守护线程设置方式

Thread t1=new Thread(new MyRunnable());
//设置为守护线程需要在执行start之前
t1.setDaemon(true);
t1.start();

The Java Virtual Machine exits when the only threads running are all daemon threads.

这句话来自 JDK 官方文档,意思是:

当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。

可以做个对比实验,设置一个用户线程,while(true)死循环执行,当主线程执行完毕退出时,jvm不会退出,因为要等待所有用户线程结束。

而这个用户线程设置为守护线程时,当主线程退出时,JVM 检测到没有其他用户线程在运行,会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事。

2、守护线程的作用及应用场景

上面,我们已经知道了,如果 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。换句话说,守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。

JVM 中的垃圾回收线程就是典型的守护线程,如果说不具备该特性,会发生什么呢?

当 JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬了!!!由此可见,守护线程的重要性了。

通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。

有几点需要注意:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值