Java并发1——线程创建、启动、生命周期与线程控制
内容提要:
- 线程与进程
为什么要使用多线程/进程?线程与进程的区别?线程对比进程的优势?Java中有多进程吗? - 线程的创建与启动
线程的创建有哪几种方式?它们之间有什么区别? - 线程的生命周期与线程控制
线程的生命周期有哪几种状态?各种状态之间如何转换?线程的等待、退让、中断等
1.线程与进程
使用多进程和多线程可以实现多个任务的并发执行,方便将IO操作或者耗时操作在后台处理,避免长时间等待,对于多核处理器能充分利用CPU资源,提高CPU使用率。
进程是系统进行调度和资源分配的独立单位。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须要有一个父进程。
线程可以拥有自己的堆栈、程序计数器、局部变量,但不拥有系统资源,它与父进程中的其他线程共享该进程的全部资源。
多线程的优点:
- 线程之间能方便的共享内存
- 创建线程的开销更小,比创建线程效率高
- Java内置了多线程支持,而不是单纯作为底层操作系统的调度方式。
Java并发中提到的都是多线程并发,Java中有多进程吗?
直接引用CSDN论坛里的回答
java实现的是一种多线程的机制,就java本身概念而言(虚拟机规范),线程级别的。
但是java到底是多进程的还是多线程的,根本由操作系统本身来决定,并不由java来决定,因为进程与线程的这种机制本身就只取决于操作系统,而不取决于高级语言语言,对于内存分配以及cpu时间片段的分配利用,是由更低级的比操作系统低的语言来实现。
对于一些老式的unix操作系统,它是没有线程概念存在的,它的异步协作方式就是多进程共享内存的方式来完成的,因此,在这种操作系统上,根本就不存在线程,java也没法实现线程,因此java就是多进程的应用程序,由多个java进程来完成协作。然而在windows上面,进程间的内存空间是互相独立的,数据不能直接共享,它的异步协作方式由进程中的线程来完成,这些线程共享进程所属内存来完成异步协作,所以java在这种操作系统上,表现的就是单进程多线程的方式。
就进程与线程的概念,并不是java本身一个概念,它们是操作系统级别的概念,java只是将操作系统的这种方式进行了包装,而并非自己去实现一套cpu时钟与内存访问机制,java本身是跳不出操作系统层面的。
2.线程的创建与启动
Java中使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
线程的创建主要有这几种方式:
- 继承Thread类创建线程类
- 实现Runnable接口创建线程类
- 通过Callable和Future创建线程
继承Thread类创建线程类
继承Thread类,重写run()方法,创建这个类的实例,调用start()方法启动线程
演示代码如下:
public class MyThread extends Thread{
public void run(){
System.out.printf("%s运行中\n",getName());
}
public static void main(String[] args) {
System.out.println("Hello\n");
MyThread mth1 = new MyThread();
mth1.start();
MyThread mth2 = new MyThread();
mth2.start();
}
}
运行结果:
Thread中start()与run()方法的区别:
void start()
启动线程,线程将处于就绪状态,并没有运行,一旦得到CPU,将开始执行run()方法。run方法也叫线程体,是线程要执行的内容。void run()
run方法是该类中的一个普通方法,如果直接调用mthX.run(),程序运行仍然只有一个线程。
Thread类实现了Runnable接口,该run方法其实就是Runnable接口的run方法。
实现Runnable接口创建线程类
定义Runnable接口的实现类,并重写该接口的run()方法,创建类的实例,并以该实例作为Thread的Runnable参数创建Thread对象
真正的线程对象是Thread对象,而不是Runnable接口实现类的实例。
示例代码:
public class MyThread implements Runnable{
public void run(){
System.out.printf("%s运行中\n",Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.println("Hello\n");
MyThread myThread = new MyThread();
Thread mth1 = new Thread(myThread);
Thread mth2 = new Thread(myThread);
mth1.start();
mth2.start();
}
}
运行结果与上面是相同的。
不同的是:getName()是Thread类的继承方法,因此继承Thread类方式中直接使用this即代表当前线程,实现Runnable则要通过Thread.currentThread().getName()调用。
API:
Thread(Runnable target)
构造一个新线程,并调用给定目标的run()方法Runnable接口:
该接口中只有一个方法run(),必须覆盖这个方法
Runnable接口也是函数式接口,因此可以使用Lambda表达式创建Runnable对象,简化代码。
将上面代码改为使用lambda表达式:
public class MyThread{
public static void main(String[] args) {
System.out.println("Hello\n");
Runnable myRunnable= () -> {
System.out.printf("%s运行中\n",Thread.currentThread().getName());
};
Thread mth1 = new Thread(myRunnable);
Thread mth2 = new Thread(myRunnable);
mth1.start();
mth2.start();
}
}
通过Callable和Future创建线程
- Callable接口
与Runnable类似,封装了一个异步运行的任务,但是有返回值。
Callable接口是一个参数化的类型,只有一个call方法。
V call() throws Exception
//Computes a result, or throws an exception if unable to do so.
- Future
Future可以用来保存异步计算的结果,可以取消操作。
Future接口共有5个方法,如下:
boolean cancel(boolean mayInterruptIfRunning)
//Attempts to cancel execution of this task.
V get()
//Waits if necessary for the computation to complete, and then retrieves its result.
V get(long timeout, TimeUnit unit)
//Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if available.
boolean isCancelled()
//Returns true if this task was cancelled before it completed normally.
boolean isDone()
//Returns true if this task completed.
get方法可以保存结果,cancel方法可以取消操作。
- FutureTask
FutureTask包装器可以将Callable转换为Future和Runnable,它同时实现了两者的接口。
也可以直接使用Runnable作为参数构造FutureTask,两种构造方法如下:
FutureTask(Callable<V> callable)
//Creates a FutureTask that will, upon running, execute the given Callable.
FutureTask(Runnable runnable, V result)
//Creates a FutureTask that will, upon running, execute the given Runnable, and arrange that get will return the given result on successful completion.
下面是使用FutureTask和Callable的步骤:
创建Callable接口的实现类,并实现call方法,创建Callable实现类的实例
使用FutureTask类包装Callable对象,封装call方法返回值
使用FutureTask对象作为Thread对象的参数创建并启动新线程
调用FutureTask的get()方法来获得子线程返回值
示例代码:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<Integer>{
private int count = 100;
java.util.Random r=new java.util.Random();
public Integer call() throws Exception{
System.out.printf("%s运行中\n",Thread.currentThread().getName());
count -= r.nextInt(10);//产生0-10随机数
return count;
}
public static void main(String[] args) throws Exception{
System.out.println("Hello\n");
MyThread myCallable = new MyThread();
FutureTask<Integer> task1 = new FutureTask<Integer>(myCallable);
FutureTask<Integer> task2 = new FutureTask<Integer>(myCallable);
Thread mth1 = new Thread(task1);
Thread mth2 = new Thread(task2);
mth1.start();
mth2.start();
System.out.printf("task1的返回值:%d\n",task1.get());
System.out.printf("task2的返回值:%d\n",task2.get());
}
}
3.线程的生命周期与线程控制
线程状态:
主要状态有这几种:新建、就绪、运行、阻塞、终止
- 状态转换图
最主要的状态转换:
运行-->阻塞:
sleep()、IO阻塞、等待同步锁、等待通知、suspend()
阻塞-->就绪:
sleep()、IO返回、获得同步锁、收到通知、resume()
线程控制:
stop, suspend and resume
容易造成死锁 弃用
sleep
线程休眠进入阻塞状态
public static void sleep(long millis)
throws InterruptedException
public static void sleep(long millis,int nanos)
throws InterruptedException
yield
线程让步,yield与sleep类似,但调用yield之后线程进入就绪态而不是阻塞态,调度器会重新进行一次调度,该线程也可能又再次执行。也即一个线程调用yield暂停后,相同优先级或更高优先级且已经处于就绪态的线程才可能获得执行机会。
join
public final void join() throws InterruptedException
等待线程终止,A线程调用B.join()方法后,A线程被阻塞,直到B线程执行结束为止。
如此可在main线程中启动多个线程执行分解任务,然后调用join方法,等待其他线程执行完成,再综合各线程结果,得到最终结果。
join也可以添加等待时间
void join(long millis)
void join(long millis, int nanos)
// join()就相当于 join(0)
setPriority
public final void setPriority(int newPriority)
设置优先级,它会首先调用checkAccess()检查是否有权限修改线程,同时newPriority要在 MIN_PRIORITY to MAX_PRIORITY范围内。
异常:
Throws:
IllegalArgumentException - If the priority is not in the range MIN_PRIORITY to MAX_PRIORITY.(1--10,默认优先级为5)
SecurityException - if the current thread cannot modify this thread.
要获得优先级,可以使用 public final int getPriority()
setDaemon
public final void setDaemon(boolean on)
设置守护线程/后台线程,这一方法要在线程启动前调用。
守护线程主要用来为其他线程提供服务,守护线程应该永远不去访问固有资源,如文件、数据库等,因为它会在任何时候甚至在操作的中间发生中断。
interrupt
线程终止:
- run方法执行结束
- return语句返回
- 未捕获异常终止
- 强制终止:interrupt
interrupt值得注意的三点:
中断状态
interrupt可以请求终止线程,对一个线程调用interrupt后,线程的中断状态被置位,但该线程并没有终止,可以看成对该线程的一个中断请求/通知,被"中断"的线程可以决定如何响应中断,也可以不响应中断,继续执行。阻塞与interrupt
当在一个被阻塞(调用sleep或wait)的线程调用interrupt方法时,会产生Interrupted Exception 异常中断;如果一个线程在中断状态被置位后调用sleep,它不会休眠,相反会清除这一状态并抛出InterruptedException。interrupted() 与 isInterrupted()
它们都用来检测线程是否被中断.
前者是静态方法, static boolean interrupted(),调用会清除该线程的中断状态,只能检测当前线程中断状态:Thread.currentThread().isInterrupted
后者是实例方法,boolean isInterrupted(),调用不会改变中断状态,可以对某个线程调用。
下面代码会涉及这几个方面:
public class MyThread{
public static void main(String[] args) {
Runnable myRunnable= () -> {
System.out.printf("%s运行中\n",Thread.currentThread().getName());
int i = 10;
//3.新线程中处理中断
try{
while(!Thread.currentThread().isInterrupted()&&i>0){
i--;
Thread.currentThread().sleep(100);
}
}
catch(InterruptedException e){
System.out.printf("%s发生异常\n",Thread.currentThread().getName());
return;
}
System.out.printf("%s退出\n",Thread.currentThread().getName());
};
Thread thread = Thread.currentThread();
//1.当前线程中断后调用sleep
System.out.printf("main线程运行中\n");
System.out.printf("main线程 isInterrupted:%s\n",thread.isInterrupted());
thread.interrupt();
System.out.printf("main线程 isInterrupted:%s\n",thread.isInterrupted());
try{
thread.sleep(1000);
}catch(Exception e){
System.out.printf("main线程发生异常\n\n");
}
//2.两次调用interrupted
System.out.printf("main线程 isInterrupted:%s\n",thread.isInterrupted());
thread.interrupt();
System.out.printf("main线程 interrupted:%s\n",thread.interrupted());
System.out.printf("main线程 interrupted:%s\n\n",thread.interrupted());
Thread mth1 = new Thread(myRunnable);
mth1.start();
//4.新线程sleep阻塞时接收到interrupt
try{
thread.sleep(1000);
}catch(Exception e){
System.out.printf("main线程发生异常:sleep\n\n");
}
mth1.interrupt();
//5.main线程等待 mth1 运行结束。
try{
mth1.join();
}catch(Exception e){
System.out.printf("main线程发生异常:join\n\n");
}
System.out.printf("\nmain线程退出\n");
}
}
运行结果如下:
Java并发1——线程创建、启动、生命周期与线程控制的更多相关文章
- Vue环境搭建-项目的创建-启动生命周期-组件的封装及应用
vue项目环境的搭建 """ node >>> python:node是用c++编写用来运行js代码的 npm(cnpm) >>> p ...
- java 多线程总结篇3之——生命周期和线程同步
一.生命周期 线程的生命周期全在一张图中,理解此图是基本: 线程状态图 一.新建和就绪状态 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Jav ...
- Java多线程与并发——线程生命周期和线程池
线程生命周期: 线程池:是预先创建线程的一种技术.线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用.减少频繁的创建和销毁对象. java里面线程池的顶级接口是E ...
- Java精选笔记_多线程(创建、生命周期及状态转换、调度、同步、通信)
线程概述 在应用程序中,不同的程序块是可以同时运行的,这种多个程序块同时运行的现象被称作并发执行. 多线程可以使程序在同一时间内完成很多操作. 多线程就是指一个应用程序中有多条并发执行的线索,每条线索 ...
- java多线程(2)---生命周期、线程通讯
java生命周期.线程通讯 一.生命周期 有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别. 1.yield()方法 yield()让当前正在运行的线程回到就绪 ...
- Servlet生命周期与线程安全
上一篇介绍了Servlet初始化,以及如何处理HTTP请求,实际上在这两个过程中,都伴随着Servlet的生命周期,都是Servlet生命周期的一部分.同时,由于Tomcat容器默认是采用单实例多线程 ...
- Java对象在JVM中的生命周期
当你通过new语句创建一个java对象时,JVM就会为这个对象分配一块内存空间,只要这个对象被引用变量引用了,那么这个对象就会一直驻留在内存中,否则,它就会结束生命周期,JVM会在合适的时 ...
- 深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
- iOS viewController 和 view 的创建消失生命周期总结
控制器创建的生命周期 1. 如果从stroryBoard 中产生一个controller,那么会先调用initWithCoder:, awakeFromNib, loadView,viewDidLoa ...
随机推荐
- [LeetCode] Flip Game 翻转游戏
You are playing the following Flip Game with your friend: Given a string that contains only these tw ...
- [LeetCode] Department Top Three Salaries 系里前三高薪水
The Employee table holds all employees. Every employee has an Id, and there is also a column for the ...
- [LeetCode] Majority Element 求众数
Given an array of size n, find the majority element. The majority element is the element that appear ...
- canvas-图片翻转
图片90度翻转 在canvas中插入图片需先加载图片(利用Image对象);加载完成后再执行操作drawImage(obj,x,y,w,h) 插入图片的坐标宽高等值 <!DOCTYPE html ...
- py-faster-rcnn之python引入_caffe.so
本文并不给出"编写一个c++代码,然后编译为.so文件,然后在python中引入"的hello world,需要的请参考:http://www.oschina.net/questi ...
- UVA1586
#include<stdio.h> #include<string.h> #include<ctype.h> int main(){ int n; ]; int n ...
- 美国在研新药_读取单个PDF
QQ:231469242 读取下载美国在研新药PDF内数据:unii,分子式,分子重量,药品名,who,编码,.... PDF无逻辑规则,不能百分之百提取,只能部分提取 几个默认字段为空 # -*- ...
- pdf在线处理网站
https://smallpdf.com/unlock-pdf
- oracle 12c 加入系统服务
1修改oratab文件 vi /etc/oratab #把后台一行的N改为Y db01:/usr/oracle/app/product/11.2.0/dbhome_1:Y 2如果安装时.bash_pr ...
- 关于C中内存操作
from:http://blog.csdn.net/shuaishuai80/article/details/6140979 malloc.calloc.realloc的区别 C Language ...