目录
一、什么是线程
进程: 进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。
线程:是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
Java中线程是指java.lang.Thread类的一个实例或线程的执行。使用java.lang.Thread或java.lang.Runnable接口编写代码定义、实例化、启动新线程。
Java中每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行。main()方法运行在一个线程内,称为主线程。一旦创建一个新的线程,就产生一个新的调用栈。
线程分为两类:用户线程和守候线程。当所有用户线程执行完毕后,JVM自动关闭。但是守候线程却不独立与JVM,守候线程一般是有操作系统或用户自己创建的。
二、如何定义线程
Callable是一种更好的抽象:它认为主入口点(call())将返回一个值,并可能抛出一个异常
- extends Thread
- implements Runnable
- 使用Future和Callable
1.继承java.lang.Thread类
①
public class Mythread extends Thread{
public void run() {
for (int i=0;i<10;i++) {
System.out.println("hello!");
}
}
}
public class ThreadDome1 {
public static void main(String[] args) {
Thread t = new Mythread();
Thread t1 = new Mythread1();
/*
* 当start方法执行后,线程纳入线程调度。
* 一旦被分配CPU时间片,线程就会自动调用run方法
*
*/
t.start(); //线程启动时调用start方法
t1.start();
}
}
②内部类创建
Thread t = new Thread() {
public void run() {
for(int i=0;i<10;i++) {
System.out.println("线程");
}
}
};
2.实现Runnable接口
①
public class ThreadDome {
MyRunnable R = new MyRunnable();
MyRunnable1 R2 = new MyRunnable1();
Thread A = new Thread(R);
Thread B = new Thread(R2);
}
class MyRunnable implements Runnable{
public void run() {
for (int i=0;i<10;i++) {
System.out.println("11111");
}
}
}
class MyRunnable1 implements Runnable{
public void run() {
for(int i=0;i<10;i++ ) {
System.out.println("22222");
}
}
}
②内部类
Runnable r = new Runnable() {
public void run() {
for(int i=0;i<10;i++) {
System.out.println("线程");
}
}
};
Thread t = new Thread(r);
3.Future/Callable
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> future = executorService.submit(task);
executorService.shutdown();
System.out.println("主线程在执行任务...");
try {
Thread.sleep(2000);
} catch(InterruptedException ex) {
ex.printStackTrace();
}
try {
System.out.println("task运行结果:"+future.get());
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
}
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程在执行任务...");
//模拟任务耗时
Thread.sleep(5000);
return 1000;
}
}
//运行结果
子线程在执行任务...
主线程在执行任务...
task运行结果:1000
所有任务执行完毕
三、实例化
1. 如果是扩展了java.lang.Thread类的线程,则直接调用new即可。
2. 如果是实现了jav.lang.Runnable接口的类,则调用Thread的构造方法:
Thread(Runnable target)
Thread(Runnable target,String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
四、启动线程
在线程的Thread对象上调用start()方法
注意:
子线程一旦启动,其地位和主线程是一样的,所以一旦主线程结束了,子线程不会受影响,不会跟着结束;
线程对象的isAlive()方法在就绪,运行,阻塞时返回true,在新建,死亡时返回false;
对已经死亡的线程调用start()是无效的,会抛出异常。 死亡的线程不可再次作为线程来执行;
对于新建的线程,调用两次start()方法也会抛出异常;
五、线程的生命周期
①新建
程序使用new会新建一个线程,new出的对象跟普通对象一样,JVM会为其分配内存,初始化成员变量等,此时线程并没有运行,而是就是新建状态。
②就绪
当线程对象调用start后,线程将进入就绪状态。JVM会为其创建函数调度栈和计数器,但此时线程依然没有运行,而是等待获取CPU执行片
③运行和阻塞
当就绪状态的线程获取了CPU执行片的之后,就进入运行状态, 但是在执行过程中,可能会因为以下原因使线程进入阻塞状态:
- CPU执行片已经用完,JVM切换到其他线程执行
- 线程调用sleep()
- 线程调用了阻塞IO方法,该方法返回之前,线程会一直阻塞
- 线程试图获取被其他线程持有的同步监视器
- 线程在等待某个通知
- 程序调用了线程的suspend()将线程挂起。(容易死锁,不推荐)
线程从运行进入阻塞状态之后,接着只能继续阻塞或者再次进入就绪状态,下面情况会使线程由阻塞状态重新进入就绪状态:
- 线程调用的slee()经过了指定时间
- 线程调用的阻塞IO方法返回
- 线程成功获取同步监视器
- 线程收到其他线程发出的通知
- 被挂起(suspend)的线程又被程序调用了resume方法
④死亡
程结束后就处于死亡状态,线程会以如下三种方式结束,
- run()或call()正常执行完成,线程正常结束
- 线程抛出一个未捕获的Exception或Error
- 直接调用线程的stop()方法结束线程,容易死锁
六、常用方法
1.currentThread()方法
Thread.currentThread() 返回当前运行的线程的引用
2.sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
3.yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
4.getId()方法
getId()的作用是取得线程的唯一标识
5.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
6.join()方法
方法join()的作用是等待线程对象销毁。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
7.getName()和setName()
用来得到或者设置线程名称
8.getPriority()和setPriority()
用来获取和设置线程优先级。
Java中线程的优先级分为1-10这10个等级,如果小于1或大于10则JDK抛出IllegalArgumentException()的异常,默认优先级是5。在Java中线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。注意程序正确性不能依赖线程的优先级高低,因为操作系统可以完全不理会Java线程对于优先级的决定。
9.setDaemon()和isDaemon()
用来设置线程是否成为守护线程和判断线程是否是守护线程。
Java中有两种线程:一种是用户线程,另一种是守护线程。当进程中不存在非守护线程了,则守护线程自动销毁。通过setDaemon(true)设置线程为后台线程。注意thread.setDaemon(true)必须在thread.start()之前设置,否则会报IllegalThreadStateException异常;在Daemon线程中产生的新线程也是Daemon的;在使用ExecutorService等多线程框架时,会把守护线程转换为用户线程,并且也会把优先级设置为Thread.NORM_PRIORITY。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
参考文章: