进程:现在的操作系统几乎都支持多任务执行,对计算机来说,每一个任务就是一个进程。
线程:一个进程可能会派生或者创建很多线程,线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期。结合操作系统知识来讲,一个进程中有很多个线程,它们的运行其实和cpu执行时间片有关,因为cpu处理的速度很快,多个线程执行时切换的速度很快,所以看起来像是同时执行(并发的)。
创建线程:构造Thread类。
Thread run()源码:
@Override
public void run(){
//如果构造Thread时传递了Runnable,则会执行runnable的run()
if(target != null){
target.run();
}
//否则需要重写Thread的run()
}
真正的逻辑在run()中,是线程的执行单元,用start()方法启动线程。
实现线程的执行单元有两种方式:
(1)重写Thread的run(),实际上Thread实现了Runnable接口,该run()不能共享,线程A不能把B的run()当做自己的执行单元。
Thread实现了Runnable源码:
//截取了一小段代码
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
//其他属性、方法等
//......
}
(2)实现Runnable的run(),并将Runnable实例用作构造Thread的参数,可以共享,使用同一个Runnable的实例构造不同的Thread实例。
实现Runnable,将线程的控制和业务逻辑彻底分离开来。
Runnable接口源码:
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread
*/
public abstract void run();
}
模拟营业大厅叫号机,使用同一个Runnable,资源共享:
/**
* 模拟营业大厅叫号机,使用同一个Runnable,资源共享
*/
public class TicketRunnable implements Runnable{
private int index = 1;
private final static int MAX = 50;
@Override
public void run() {
while(index <= MAX){
System.out.println(Thread.currentThread() + "的号码是" + (index++));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]){
final TicketRunnable ticketRunnable = new TicketRunnable();
Thread thread1 = new Thread(ticketRunnable,"一号窗口");
Thread thread2 = new Thread(ticketRunnable,"二号窗口");
Thread thread3 = new Thread(ticketRunnable,"三号窗口");
Thread thread4 = new Thread(ticketRunnable,"四号窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
线程生命周期状态图:
此时可能发生如下状态转变:
(1)调用stop方法或者判断某个逻辑标识-->terminated(结束)
(2)调用sleep或者wait而进入wait set-->blocked(阻塞)
(3)获取某个锁资源,加入到该锁的阻塞队列中-->blocked
(4)cpu调度器轮询该线程放弃执行-->runnable
(5)调用yield(),放弃cpu执行权-->runnable
此时可以切换以下状态:
(1)调用stop方法或者意外死亡-->terminated(结束)
(2)线程阻塞结束,如读取到想要的数据字节-->runnable
(3)线程完成了指定时间的休眠-->runnable
(4)wait中线程被其他线程notify/notifyAll-->runnable
(5)线程获取到了某个锁资源-->runnable
(6)线程阻塞过程中被打断,如其他线程调用interrupt()-->runnable
(1)sleep():给定一个休眠的时间,以系统的定时器和调度器的精度为准,不会放弃monitor锁的所有权
TimeUnit可代替sleep
(2)yield():提醒调度器放弃当前cpu资源,若当前资源不紧张,调度器会忽略这种提醒,线程running-->runnable
(3)interrupt():
一般来讲,调用这些方法会使得当前线程进入阻塞状态:
另外一个线程调用被阻塞线程的interrupt方法,会打断这种阻塞,称为可中断方法。
一个线程的阻塞状态被打断,会抛出一个InterruptedException。
(4)isInterrupted():Thread的一个成员方法,判断当前线程是否被中断,仅仅是对interrupt标识的判断,可中断方法如sleep()获得中断信号(捕获到InterruptredException)之后会擦除interrupt标识。
(5)interrupted():是一个静态的方法,也用于判断当前线程是否被中断,调用该方法会直接擦除interrupt标识,若当前线程被打断,第一次调用interrupted()会返回true并立即擦除了interrupt标识,第二次及以后的调用都会返回false,除非在此期间又一次被打断。
(6)join():与sleep一样是可中断方法。join某个线程A,会使得当前线程B进入等待,直到线程A结束生命周期,或者到达给定的时间。通常都是main线程等到其他多个线程执行完毕后再继续执行,其他多个线程之间并不需要互相等待。
(1)线程运行结束,完成使命正常退出。
(2)捕获中断信号关闭线程。
(3)使用volatile修饰的flag开关控制。
(4)RuntimeException 异常退出。
(5)进程假死(某个线程阻塞,死锁)。