栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程基础

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程基础

线程、进程的概念与区别 进程

当我们在桌面点击一个应用时(QQ),系统就会将程序加载到内存中,也就是说进入到内存中的程序就是一个进程,一个程序可以运行多个进程(比如同时登录几个QQ),当使用任务管理器关闭程序时,进程也就从内存中清除。

线程

线程时进程的一个执行单元,负责进程中程序的执行(QQ中发消息是一个线程,添加好友是一个线程),一个进程中至少有一个线程。

区别

进程是资源分配单元,线程是执行单元。

需要知道的是,java本身不能创建线程,线程是由操作系统管理的,java所谓的支持多线程,只不过是java可以调用操作系统资源创建多线程。

创建多线程的2中常见方式:

方式1:继承Thread类,重写run()方法

public class ThreadDemo01 extends Thread{
	@Override
    public void run() {
        System.out.println("Thread is running");
    }
}

public static void main(String[] args) {
	ThreadDemo01 thread01 = new ThreadDemo01();
	//开启线程:线程等到cpu的执行权后执行run()方法中的代码
	thread01.start();
}

方式2:实现Runnable接口,实现run()方法

public class ThreadDemo02 implements Runnable{
	public void run(){
		 System.out.println("Thread is running");
	}
	public static void main(String[] args) {
		ThreadDemo02 thread02=	new ThreadDemo02();
		//调用thread类中的构造方法,传入实例,创建线程对象
		Thread t = new Thread(thread02);
		t.start();
	}
}
Thread 源码简看(部分代码)
public class Thread implements Runnable {
    
    private Runnable target;
   
    // 构造方法
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    // 构造方法
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

解读一下创建前面提到的创建线程的两种方式:
方式1:通过Thread类的无参构造器创建Thread对象,开启线程。
方式2:通过Thread类的有参构造器创建Thread对象,参数类型为Runnable类型(所以参数应该是实现了Runnable接口的实现类),Thread类继承Runnable接口,实现了run()方法,可以看到run()方法中,还是调用了实现了Runnable接口的实现类中的run()方法。

Runnable 接口源码


接口中只有一个抽象方法,它有什么作用呢?
1.限定了Thread类的有参构造的参数类型
2.将run()方法抽取出来,让实现类去重写此方法

发现

一个线程的执行,总是从start()方法开始,可以看出,它就是开启线程的钥匙,线程开启后,会自动调用Thread类中的run()方法(无论是有参还是无参,都会调用Thread中的run()方法),所以说Thread类(以及子类)是线程运行的入口。

继承Thread类 VS 实现Runnable接口

从上面的两个demo中可以看出:
1.继承Thread类,需要重写run()方法,这样线程和待执行的方法在一个类中,无法坐到解耦。
2.实现Runnable接口,实现类重写run()方法,实现类作为一个参数传入Thread类中,线程和待执行的代码是分离开来的。

PS:
创建线程,一般会有匿名内部类的方式

方式1:
new Thread(){
            @Override
            public void run() {
                System.out.println("The code waiting for Thread1");
            }
        }.start();
方式2(只用一次):
new Thread(new Runnable() {
            public void run() {
                System.out.println("The code waiting for Thread2");
            }
        }).start();
方式3(多个线程共享):
        Runnable r = new Runnable() {
            System.out.println("The code waiting for Threads");
        };
        Thread t1  = new Thread(r);
        Thread t2  = new Thread(r);
        Thread t3  = new Thread(r);
        Thread t4  = new Thread(r);
        Thread t5  = new Thread(r);
        t1.start();
        t2.start();
线程类和任务类

Thread是线程类,另外两个是任务类
实际上只有Thread类通过start()方法调用操作系统创建线程,而任务类(Runable,Callable)并不会产生线程,从上面的例子中可以看出,它们只是提供了待执行的代码(run()方法)。
为了让Thread和Runnable/Callable产生关联,通常需要我们手动进行“组合”(Thread只能接收Runnable,Callable要使用线程池,后面再介绍)。

小结

要想让多线程执行一个任务,大致有以下几种方法:
1.线程与任务不分离:
重写Thread类中的run()方法,把任务直接放到Thread类内部
2.线程与任务不分离:
实现Runnable接口,把任务放到实现类中,再把实现类放到Thread类内部;
实现Runnabel/Callable接口,丢到线程池中

public class test01 extends Thread{


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //方式1,重写Thread类中的run方法
       Thread t1 =  new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"正在执行");
            }
        };
       t1.start();

       //方式2:把runnable接口的实现类传入到Thread类中的构造器中
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"正在执行");
            }
        }).start();
        //方式3:线程池+callable/runnable
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future string = executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "正在执行");
            return "success";
        });
        String s = string.get();
        System.out.println(s);
        //线程池关闭
        executorService.shutdown();
    }
}
FutureTask

前面我们提到过callable接口,但具体怎么用的,没有详细讲,在说callable接口之前,需要先知道FutureTask是什么?
先看一下FutureTask怎么用

public class test02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //把Callable接口放入到FutureTask中
        FutureTask futureTask = new FutureTask<>(new Callable() {
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"正在执行");
                return "success";
            }
        });

        //又把FutureTask放入到Thread类中
        new Thread(futureTask).start();
        System.out.println(Thread.currentThread().getName()+"启动任务");

        //获取一部结果
        String s = futureTask.get();
        System.out.println(s);

    }
}

是不是有点蒙,我们来看一下FutureTask的继承体系

实现了runnable接口,这就能理解为什么可以当做参数传入到Thread类的构造器中了,但把Callable塞到FutureTask,然后又把FutureTask塞到Thread中,这又是搞什么呢?
FutureTask有两个特征:
1.能包装Runnable和Callable,(案例中只包装了callable)
2.通过get()方法,可以等到返回值

先验证一下,FutureTask可以包装runnable和callable
public class test03 {
    public static void main(String[] args) {
        //包装callable
        FutureTask callAbleTask = new FutureTask<>(new Callable() {

            @Override
            public String call() throws Exception {
                System.out.println("callable被包装了");
                return "callable";
            }
        });
        Thread thread = new Thread(callAbleTask);
        thread.start();

        //包装runnable
        FutureTask runnableTasl = new FutureTask<>(new Runnable() {

            @Override
            public void run() {
                System.out.println("runnable被包装了");
            }
        }, "runnable");
        Thread thread1 = new Thread(runnableTasl);
        thread1.start();
    }
}

那么问题来了,FutureTask是如何包装runnable和callable呢?
猜测可能是FutureTask类中有两个成员变量分别来接收runnable和callable类型,就让我们看看吧:

可是看了看FutureTask,发现只有一个Callable类型的成员变量,这是什么情况,和我们想的不一样呀,那Runnable怎么存的呢?


可以看到FutureTask的两个有参构造的不同,如果传入的是runnable类型的参数,调用了Executors.callable()方法:


可以看到传入的是runnable类型的参数,最终还是会调用runnable的run()方法,并且把传入的参数挡当作返回值.最终把runnable类型转换成callable类型赋值给callable类型的成员变量。

Runnable和Callable的返回值问题

callable中的call()方法是有返回值的,而runnable中的run()方法没有返回值,把他们都包装成FutureTask后,又是怎么解决的呢?

就是在这里做的,如果是runnable类型的参数,会返回事先传入的result。

获取结果

怎么获取结果的呢?
上面的案例中,可以通过get()方法,获取返回值,get()方法是一个阻塞的方法.

为什么get()方法是一个阻塞方法

在FutureTask中定义了很多任务状态:

一个任务,有时可能非常耗时。而当用户使用futureTask.get()时,必然是希望获取最终结果的。如果FutureTask不帮我们阻塞,就有可能获取空结果。
所以返回的futureTask并不是真正的结果,真正的结果是FutureTask类中成员变量outcome,线程执行等到的结果由于要耗时,可能outcome为null,建议不要立即调用get()方法。

当是某个状态的时候,就会执行阻塞方法,在阻塞方法放行执行,outcome的值是为null的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/873838.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号