1.进程与线程
- 进程
- 进程是操作系统进行资源分配的最小单位
- 资源包括:cpu,内存空间和磁盘等,同一个进程可以有多条线程,且这多条线程共享该进程中的全部系统资源
- 进程与进程之间是相互独立的
- 进程是程序在计算机上的一次执行活动,当运行一个程序,就启动一个进程
- 进程分为系统进程和用户进程
- 线程
- 线程是CPU调度的最小单位,线程必须依赖于进程而存在
- 线程自己不拥有系统资源,只有一点在运行中的资源(程序计数器,寄存器和栈),但是它可以与同属于一个进程的其他线程共享进程所拥有的全部资源
- CPU时间片轮转机制
- 当我们在一台4核CPU的电脑上运行多个程序时(假设有100个进程),但是我们的操作并不会因为进程数而受影响;--因为操作系统提供了一种CPU时间片轮转机制
- 时间片轮转机制又称RR调度算法,每个进程被分配一个时间段,如果在时间片结束时程序还在运行,则CPU会被剥夺并分配给另一个进程。如果进程在时间片结束之前阻塞或结束,则CPU当即进行切换
2.线程的创建
类Thread,接口Runnable,接口Callable
public class ThreadDemo {
static class UseThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("====== UseThread run");
}
}
static class UseRunnable implements Runnable {
@Override
public void run() {
System.out.println("====== UseRunnable run");
}
}
static class UseCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("====== UseCallable run");
return "UseCallable call result";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方式一
UseThread useThread = new UseThread();
useThread.start();
//方式二
UseRunnable useRunnable = new UseRunnable();
new Thread(useRunnable).start();
/**
* 方式2.1:
* 前面两种创建线程的方式,并在线程中执行run方法没有返回值
* 如果要实现在线程中运行,并且拿到返回值就需要使用FutureTask方法创建线程
*/
UseCallable useCallable = new UseCallable();
FutureTask<String> futureTask = new FutureTask<String>(useCallable);
new Thread(futureTask).start();
System.out.println("get result:"+futureTask.get());
}
}
打印:
====== UseThread run
====== UseRunnable run
====== UseCallable run
get result:UseCallable call result
线程启动方式有三种:
X extends Thread: 然后X.start
X implements Runnable: 然后交给Thread运行
X implements Callable: 然后交给Thread运行
第1,2种方式都有一个缺陷就是:在线程执行完任务之后无法获取执行结果。所以从Java1.5开始,
就提供了Callable和Future,通过他们可以在任务执行完毕之后得到任务执行结果。
纠正:常见线程的方式只有两种
* The other way to create a thread is to declare a class that
* implements the <code>Runnable</code> interface. That class then
* implements the <code>run</code> method.
2.1.线程终止
- 线程中止分为自然终止和手动终止
- 自然终止要么是run方法执行完成了,要么是抛出了一个未处理的异常导致线程提前结束
- 手动终止:
- 线程Thread有提供对应操作API,如暂停suspend(),恢复resume(),停止stop();但是这写API是过期的,不建议使用。原因是以suspend()方法为例,在调用后,线程不会释放已经占有的资源(锁),而是占有者资源进入睡眠状态,这样容易引起死锁问题。
- 安全的终止则是其他线程通过调用某个A线程的interrupe()方法对其进行中断操作,中断好比其他线程对该线程打了个招呼:"A,你要中断了",不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求,因为java里的线程是协作式的,不是抢占式的。
- 线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标记位改为false。
- 如果一个线程处于阻塞状态(sleep,join,wait),则线程在检查中断标识时如果发现中断标识为true,则会在这写阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标识位清除,即重新设置位false
- 不建议自定义一个取消标志位来终止线程的运行。因为run方法里有阻塞调用时无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。
- 注意处于死锁状态的线程无法被中断
//使用interrupt发送中断信息终止
static class DemoThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("interrupt flag:"+isInterrupted());
// while (true){
while (!isInterrupted()){
System.out.println("DemoThread running ...");
}
System.out.println("222 interrupt flag:"+isInterrupted());
}
}
public static void main(String[] args) {
DemoThread demoThread = new DemoThread();
demoThread.start();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
demoThread.interrupt();
}
//在线程处于阻塞状态下收到中断信息会抛出异常,且线程不会终止;这个时候可以在catch捕获到异常时再次发起中断信息
static class DemoThread2 extends Thread {
@Override
public void run() {
super.run();
System.out.println("interrupt flag:" + isInterrupted());
while (!isInterrupted()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
interrupt();
}
System.out.println("DemoThread2 running ...");
}
System.out.println("222 interrupt flag:" + isInterrupted());
}
}
3.线程生命周期
1.Thread生命周期.png3.1.线程生命周期相关方法
-
wait:拥有锁的情况下,从运行态进入阻塞态,并释放锁
- wait和notify/notifyAll必须成对出现,而且必须放在synchronized里面
-
notify/notifyAll:其他线程通过notify唤醒在此对象监视器上等待的单个/多个线程,被唤醒的线程从阻塞态进入就绪态
- 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程(随机),直到当前的线程放弃此对象上的锁,才能继续执行被唤醒的线程
-
sleep:拥有锁的情况下,从运行态进入阻塞态,不释放锁
- 按指定时间内暂停当前正在执行的线程,不释放锁
-
yield:拥有锁的情况下,从运行态进入就绪态,不释放锁
- 暂停当前正在执行的线程,使当前线程让出CPU占有权,让更高优先级线程有机制执行
-
join:是由一个线程调用另一个线程,调用线程等待被调用线程终止(即主线程由运行态进入阻塞态),不释放锁
- 等待该线程终止
3.2.线程的七种基本状态
- 新建状态(New):线程刚被创建,但尚未启动。(如Thread t = new MyThread();)
- 就绪状态(Runnable):当调用线程对象的start方法(t.start();),线程即进入就绪状态。
- 处于就绪状态的线程,知识说明此线程已经做好了准备,随时等待CPU调度执行,所以调用了thread.start()方法并不会让该线程立即执行。
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行。
- 就绪状态时进入到运行状态的唯一入口,也就是说,线程要进入运行状态,首先必须处于就绪状态
- 无限等待(Waiting):位于对象等待池中的阻塞状态,当线程处于运行状态时,执行了某个对象的wait方法,java虚拟机会把线程放到这个对象的等待池中(_WaitSet)。
- 处于无限等待状态的线程不会被分配处理器执行时间,他们要等待被其他线程显示唤醒
- 限期等待(Timed Waiting):处于该状态下的线程不会被分配处理器执行时间,无须等待其他线程显示唤醒,在一定时间后他们由系统自动唤醒。(Thread.sloop(timer);)
- 阻塞状态(Blocked):当线程处于运行状态时,会试图去获取某个对象的同步锁,如果该对象的同步锁已经被其他线程占用,java虚拟机会把这个线程放到对象的阻塞池中(_EntryList)
- 线程在获取synchronized同步锁失败后,会暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态,才有机会再次被CPU调用以进入运行状态
- 死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期
网友评论