内容提要:
-
线程与进程
为什么要使用多线程/进程?线程与进程的区别?线程对比进程的优势?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");
}
}
运行结果如下: