java启动线程的方法(线程池启动线程的方式)
我们在学习软件开发时,多线程,高并发是一个必不可少的知识点,也是在面试时必会问到的内容,为了让大家对多线程,高并发编程有个清晰认识,特地组织了一个专栏来专门介绍一下,希望能对大家有一些帮助。
线程简介
线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如微软早期的dos)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个主线程。
进程和线程是现代操作系统中两个必不可少的运行模型。在操作系统中可以有多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程);一个进程中可以有一个或多个线程。进程和进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间中运行的。而一个进程中的线可以共享系统分派给这个进程的内存空间。
在进一步介绍之前,我们先来了解一些基本概念,以帮助大家更快速的理解。
基本概念
进程
进程是操作系统中正在执行的不同的应用程序,例如:我们可以同时打开微信和qq,甚至更多的程序。
在操作系统中运行的程序就是进程,进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是cpu调度和执行的单位
线程
线程是一个应用程序进程中不同的执行路径,例如:我们的web服务器,能够为多个用户同时提供请求服务。
进程是不活泼的,进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个生命周期都在该进程中。
在一个进程中,如果创建了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
多线程
多线程拥有多条执行路径,「主线程与子线程并行交替执行」(普通方法只有主线程一条路径),对同一份资源操作时,会存在资源抢夺的问题,这时就需要加入并发控制了。
一个java应用程序,至少有三个线程: main()主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。同时做多个事情。比如:一边听歌、一边写代码。
- 提高计算机系统cpu的利用率。不过线程也会带来额外的开销,如cpu调度时间,并发控制带来的系统开销。
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
何时需要多线程?
程序需要同时执行两个或多个任务。
需要一些后台运行的程序时,比如:java后台运行的gc线程。
创建线程
java中创建线程有四种方式,我们下面依次介绍一下。
1、继承 thread 类
(1)定义thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建thread子类的实例对象,即创建了一个线程对象。
(3)调用该线程对象的start()方法来启动该线程。
示例代码:
public class mythread extends thread {
// 总票数
public int count = 10;
@override
public void run() {
// 当还有票时就继续售卖
while (count > 0) {
// 剩余票数
count--;
system.out.println(
thread.currentthread().getname() + "售卖第 " + (10 - count) + " 张票,当前剩余票数: " + count);
}
}
public static void main(string[] args) {
mythread mythread = new mythread();
mythread.start();
}
}
2、实现runnable接口
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 runnable实现类的实例对象,并将该实例作为thread的target来创建一个thread对象,该thread对象才是真正的线程运行对象。
(3)调用该线程对象的start()方法来启动线程。
示例代码:
public class myrunablethread implements runnable {
// 总票数
public int count = 10;
@override
public void run() {
// 当还有票时就继续售卖
while (count > 0) {
// 剩余票数
count--;
system.out.println(
thread.currentthread().getname() + "售卖第 " + (10 - count) + " 张票,当前剩余票数: " + count);
}
}
public static void main(string[] args) {
myrunablethread myrunablethread = new myrunablethread();
thread mythread = new thread(myrunablethread);
mythread.start();
}
}
「thread 和 runnable 的区别」
上述两种方法是大家最常见到的两种创建线程的方法,也常常会被问到两种方式创建线程的区别,下面简单总结了一下:
「继承 thread 类」
子类继承 thread 类具备多线程能力
启动线程:子类线程对象调用 .start()方法
不建议使用:避免 oop 单继承局限性
「实现接口 runnable」
- 具有多线程能力
- 启动线程:传入目标对象 + thread对象调用.start()方法
- 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用
另外,在使用线程池时只能放入实现runable或callable类线程,不能直接放入继承thread的类
3、实现callable接口
(1)创建callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建callable实现类的实例,使用futuretask类来包装callable对象,该futuretask对象封装了该callable对象的call()方法的返回值。
(3)使用futuretask对象作为thread对象的target创建并启动新线程。
(4)调用futuretask对象的get()方法来获得子线程执行结束后的返回值。
示例代码:
public class mycallablethread implements callable<string> {
// 总票数
private int count = 10;
@override
public string call() throws exception {
// 当还有票时就继续售卖
while (count > 0) {
// 剩余票数
count--;
system.out.println(
thread.currentthread().getname() + "售卖第 " + (10 - count) + " 张票,当前剩余票数: " + count);
}
return "票已售完";
}
public static void main(string[] args) throws interruptedexception, executionexception {
callable<string> callable = new mycallablethread();
futuretask<string> futuretask = new futuretask<>(callable);
thread mythread = new thread(futuretask);
mythread.start();
// 打印返回结果
system.out.println(futuretask.get());
}
}
「runnable和callable的区别:」
callable规定的方法是call(),runnable规定的方法是run()。
callable的任务执行后可返回值,而runnable的任务是不能有返回值。
call方法可以抛出异常,run方法不可以。
4、线程池
java默认提供了五种线程池,通过executors创建,分别为:
- 「newcachedthreadpool」 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- 「newfixedthreadpool」 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- 「newscheduledthreadpool」 创建一个定长线程池,支持定时及周期性任务执行。
- 「newsinglethreadexecutor」 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(fifo, lifo, 优先级)执行。
- 「newworkstealingpool」 创建一个具有抢占式操作的线程池,由于能够合理的使用cpu进行对任务操作(并行操作),所以适合使用在很耗时的任务中。
示例代码:
public class mythreadpool implements runnable {
// 总票数
public int count = 10;
@override
public void run() {
// 当还有票时就继续售卖
while (count > 0) {
// 剩余票数
count--;
system.out.println(
thread.currentthread().getname() + "售卖第 " + (10 - count) + " 张票,当前剩余票数: " + count);
}
}
public static void main(string[] args) {
executorservice ex = executors.newfixedthreadpool(5);
mythreadpool t = new mythreadpool();
ex.submit(t);
ex.shutdown();
}
}
关于线程池,后续会有单独文章给大家详细介绍。
通过以上的内容,希望大家可以对线程有个初步的认识,相关示例代码,稍后整理后我会上传到github上,也请大家留意我们的后续文章。