1.进程与线程
1.概念:
进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
线程:是进程的执行单元,CPU调度和分派的基本单位,多个线程之间共享进程的资源。
2.关系:
答:一个进程中有多个线程,线程是进程的执行单元,多个线程共享进程的资源,可以理解为线程是和进程是所属关系的。
3.区别:
进程是系统资源分配和调度的独立单位,线程是负责程序执行的执行单元。通俗的理解就是,一个java程序就是一个进程,里面包含了main线程以及其他线程。
2.串行、并行与并发
1.概念:
串行:指同一时刻只有一个任务执行。
并行:指同一时刻有多个任务同时执行。
并发:指单位时间内有多个任务同时执行。
2.区别:
串行和并行的区别很好理解,不多赘述。那并行和并发呢?
通常我们所说的都是并发编程,并发指的是cpu通过快速的上下文切换来达到了单位时间多任务同时执行的效果,而并行所要描述的是指cpu真的在同时执行多个任务。这么说完是不是有所了解了呢。
3.创建线程的三种方式
方式一:继承Thread类
public class Thread1 extends Thread {
public static void main(String[] args){
Thread thread = new Thread1();
thread.start(); // 启动线程
}
@Override
public void run() { // 线程执行的主体方法
System.out.println("thread 1");
}
}
优点:实现简单,只需实例化继承类的实例,即可使用线程
缺点:扩展性不足,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程(单继承)
方式二:实现Runnable接口
public class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("thread 2");
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
Thread thread2 = new Thread(task);
thread2.start();
}
}
优点:相较于继承Thread类,能够继承其他的类,实现其他必需的功能;更适用在多线程下的实际场景,使用线程池,共享资源等。
缺点:构造线程实例的过程相对繁琐一点
方式三:实现Callable接口
public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
return "thread 3";
}
public static void main(String[] args) {
CallableTask callableTask = new CallableTask();
FutureTask<String> futureTask = new FutureTask<>(callableTask);
Thread thread3 = new Thread(futureTask);
thread3.start();
String result = null;
try {
//获取线程执行结果
result = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
}
优点:相较于Runnable接口,支持线程返回结果。
缺点:相较于实现Runnable接口的方式,较为繁琐
4.线程状态
查看Thread的源码中发现,线程具备如下状态
/**
* 线程生命周期中的的六种状态
* NEW:还没有调用start()的线程实例所处的状态
* RUNNABLE:正在虚拟机中执行的线程所处的状态,但它可能正在等待来自于操作系统的其它资源,比如处理器。
* BLOCKED:线程阻塞于锁的状态
* WAITING:等待其它线程执行特定操作的线程所处的状态
* TIMED_WAITING:该状态不同于WAITING,它可以在指定的时间后自行返回
* TERMINATED:该线程已经执行完毕
*/
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程状态图:
上图较为清晰的描述了Java虚拟机线程状态之间切换的过程和状态切换对应的方法。下面介绍下重要的方法:
- Thread.sleep(long millis):使用线程对象调用sleep方法,使线程进入TIMED_WAITING(超时等待)状态,不释放锁,sleep时间到达后自动进入就绪态。
- Thread.yield():使用线程对象调用yield(让步)方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,主要为其他优先级高的线程做出让步。
- Thread.join()/Thread.join(long millis):把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,且不会释放持有的锁。
- Object.wait():当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
- Object.notify():唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
- LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。
此外,wait和notify需要在同步代码块中,否则会抛出InterruptedException异常。如此设计是为了避免lost wake up问题,感兴趣可以自行搜索。
5.总结
本篇就到这里了,主要介绍了多线程相关的一些基本概念、Java的线程的使用和创建方式,Java虚拟机中的线程状态和切换线程状态的常用方法。多线程的基础内容还是比较重要的,深入问题在后续的博客中会慢慢写~