进程与线程
进程: 是程序的一次动态执行过程,经历代码加载,代码执行到执行完毕的一个完整的过程。多进程操作系统能同时达运行多个进程,由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
线程: 是进程在执行过程中产生的多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。
进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。
Java 中线程实现
Java 中实现多线程有三种手段,一种是继承 Thread 类,一种就是实现 Runnable 接口, 一种就是实现 Callable 接口
继承 Thread 类方式1
2
3
4
5
6
7
8
9
10
11
12
13
14// 继承Thread类,作为线程的实现类
class ThreadExtd extends Thread{
private String name ; // 表示线程的名称
public ThreadExtd(String name){
this.name = name ; // 通过构造方法配置name属性
}
@Override
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
}
实现 Runnable 接口方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public interface Runnable {
public abstract void run();
}
// 实现Runnable接口,作为线程的实现类
class RunnableImpl implements Runnable{
private String name ; // 表示线程的名称
public RunnableImpl(String name){
this.name = name ; // 通过构造方法配置name属性
}
@Override
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
}
实现 Callable 接口方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public interface Callable<V> {
V call() throws Exception;
}
// 实现 Callable 接口,作为线程的实现类 java.util.concurrent.Callable
public class CallableImpl implements Callable<String> {
private String name ; // 表示线程的名称
public CallableImpl(String name){
this.name = name ; // 通过构造方法配置name属性
}
@Override
public String call() throws Exception { // 覆写 call() 方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
return this.name;
}
}
三者区别与联系
1 | // FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Future,实现了Runnable |
Callable 和 Runnable 的实现方式是实现其接口,支持多继承,但基本上用不到
Thread的实现方式是继承其类
Thread实现了Runnable接口并进行了扩展,Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都需要new Thread,然后执行 start 方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
实现Callable接口的任务线程能返回执行结果,Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。 Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取结果,当不调用此方法时,主线程不会阻塞。
Java 中线程 状态变化
创建状态
创建了一个线程对象后,新的线程对象便处于新建状态, 此时它已经有了相应的内存空间和其他资源1
Thread thread=new Thread();
就绪状态
调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件1
thread.start();
运行状态
线程队列中线程获得CPU资源时,线程就进入了运行状态, 此时,自动调用该线程对象的 run() 方法
阻塞状态
处于运行状态的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,此时,进入阻塞状态。或者,在运行状态下,调用了sleep(),suspend()(过时弃用),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态
死亡状态
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态
Java 中线程常用方法
thread.sleep()
线程休眠: 线程暂缓执行,进入阻塞状态,等到预计时间再执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
thread.join()
等待线程终止: 指在主线程中调用该方法时就会让主线程休眠,进入阻塞状态,让调用join()方法的线程先执行完毕后再开始执行主线程。
thread.yield()
线程让步: 暂停当前正在执行的线程对象,并执行其它线程
交出cpu, 但不释放锁,不进入阻塞状态,直接进入就绪状态
thread.interrupt()
设置中断标志: 只是改变中断状态而已,它不会中断一个正在运行的线程。具体来说就是,调用interrupt()方法只会给线程设置一个为true的中断标志,而设置之后,则根据线程当前状态进行不同的后续操作
1、如果线程的当前状态出于非阻塞状态,那么仅仅将线程的中断标志设置为true而已;
2、如果线程的当前状态出于阻塞状态,那么将在中断标志设置为true后,还会出现wait()、sleep()、join()方法之一引起的阻塞,那么会将线程的中断标志位重新设置为false,并抛出一个InterruptedException异常。
3、如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理。例如,一个线程在运行状态时,其中断标志设置为true之后,一旦线程调用了wait()、sleep()、join()方法中的一种,立马抛出一个InterruptedException异常,且中断标志被程序自动清除,重新设置为false。
调用Thread类的interrupted()方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常
object.wait()
线程等待: 让当前正在执行的线程进入线程阻塞状态的等待状态,该方法时用来将当前线程置入“预执行队列”中,并且调用wait()方法后,该线程在wait()方法所在的代码处停止执行,直到接到一些通知或被中断为止
1、wait()方法只能在同步代码块或同步方法中调用,故如果调用wait()方法时没有持有适当的锁时,就会抛出异常。
2、wait()方法执行后,当前线程释放锁并且与其他线程相互竞争重新获得锁。
object.notify()
线程唤醒: notify()方法要在同步代码块或同步方法中调用,用来通知那些等待该对象的对象锁的线程,对其调用wait()方法的对象发出通知让这些线程不再等待,继续执行.如果有多个线程都在等待,则由线程规划器随机挑选出一个呈wait状态的线程将其线程唤醒,继续执行该线程.
注意:调用notify()方法后,当前线程并不会马上释放该对象锁,要等到执行notify()方法的线程执行完才会释放对象锁
object.notifyAll()
线程唤醒: notifyAll()方法将同一对象锁的所有等待线程全部唤醒
线程状态转换关系图