一、什么是进程?什么是线程?
操作系统可以同时支持多个程序的运行,而一个程序可以狭义的认为就是一个进程。在一个进程的内部,可能包含多个顺序执行流,而每个执行流就对应一个线程。
1.1、进程
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。当程序进入内存运行时,即为开启了一个进程。
进程的特点:
- 独立性:进程是系统中独立的实体,可以拥有独立的资源
- 动态性:进程是动态的,它有自己的生命周期
- 并发性:多个进程可以并发运行,互不影响
并发性和并行性是不同的概念:并行是指同一时刻,多个命令在多个处理器上同时执行;并发是指在同一时刻,只有一条命令是在处理器上执行的,但多个进程命令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
1.2、线程
线程:是程序内部的顺序执行流。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程并不拥有自己的系统资源,它与其他线程一起分享进程的系统资源。
【举例】
public class Test { public static void main(String[] args) { Thread th = Thread.currentThread(); System.out.println("Tread name:" + th.getName()); method(); } public static void method() { Thread th = Thread.currentThread(); System.out.println("Tread name:" + th.getName()); } }
执行结果:(当java程序启动的时候,会启动一个由主方法(public static void main(String[] args))所开启的主线程main)
Tread name:main
Tread name:main
执行过程如下:
程序的执行过程:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生了,但还没有开始执行,平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了,在我们机器里面实际上运行的都是线程。
线程状态:
线程总共有5大状态
- 新建状态:新建线程对象,并没有调用start()方法之前
- 就绪状态:调用start()方法之后线程就进入就绪状态,在变为当前线程之前都是为就绪状态。值得一提的是,线程从阻塞状态中恢复的时候也会进入就绪状态
- 运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态
- 阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态
- 死亡状态:线程执行结束
二、如何创建多个线程?
2.1、继承Thread类
【举例】
public class MyThread extends Thread { private String params; public MyThread(String params) { this.params = params; } @Override public void run() { Thread thread = Thread.currentThread(); System.out.println(this.params + ":" + thread.getName()); } }
public class Test { public static void main(String[] args) { Thread th = Thread.currentThread(); System.out.println("main():" + th.getName()); threadTest1(); threadTest2(); method(); } public static void threadTest1() { MyThread myThread = new MyThread("threadTest1"); myThread.start(); } public static void threadTest2() { MyThread myThread = new MyThread("threadTest2"); myThread.run(); } public static void method() { Thread th = Thread.currentThread(); System.out.println("method():" + th.getName()); } }
执行结果:
main():main
threadTest2:main
method():main
threadTest1:Thread-0
调用myThread.start()方法,会开启一个新的线程Thread-0。而调用run()方法时,不会开启一个新的线程,仍然是单线程模式。
2.2、实现Runnable接口
【举例】
public class MyThread implements Runnable { private String params; public MyThread(String params) { this.params = params; } @Override public void run() { Thread thread = Thread.currentThread(); System.out.println(this.params + ":" + thread.getName()); } }
public class Test { public static void main(String[] args) { Thread th = Thread.currentThread(); System.out.println("main():" + th.getName()); threadTest1(); threadTest2(); method(); } public static void threadTest1() { MyThread myThread = new MyThread("threadTest1()");//这种线程创建方式有很多弊端,使用Executors提供的线程池更好 myThread.run(); } public static void threadTest2() { MyThread myThread = new MyThread("threadTest2()"); Thread thread = new Thread(myThread); thread.start(); } public static void method() { Thread th = Thread.currentThread(); System.out.println("method():" + th.getName()); } }
执行结果:
main():main
threadTest1():main
method():main
threadTest2():Thread-0
实现Runnable接口和继承Thread类这两种开辟新线程的方法,应该优先选择实现Runnable接口。因为接口可以实现多个,而类只能是单继承。
2.3、实现Callable接口
实现Callable接口的好处在于可以获取到线程的返回值,关于Callable和Future,我们会在后面再详细的介绍。
【举例】
public class CallThread implements Callable<Integer> { @Override public Integer call() { int i = 0; for (; i < 100; i++) { Thread thread = Thread.currentThread(); System.out.println(i + " " + thread.getName()); } return i; } }
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { CallThread callThread = new CallThread(); FutureTask<Integer> ft = new FutureTask<>(callThread); Thread thread = new Thread(ft);//这种线程创建方式有很多弊端,使用Executors提供的线程池更好 thread.start(); System.out.println("返回值:" + ft.get()); System.out.println("==============="); Thread thread1 = Thread.currentThread(); System.out.println("线程:" + thread1.getName()); } }
执行结果:
0 Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-0 5 Thread-0 6 Thread-0 7 Thread-0 8 Thread-0 9 Thread-0 返回值:10 =============== 线程:main
虽然程序中打开了两个线程,但是由于需要获取到Thread-0线程的返回值(ft.get()),所以Thread-0线程和main线程并不能并发执行,只有当Thread-0线程执行结束,才可以继续执行main线程。
【修改】
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //CallThread callThread = new CallThread(); //修改为lambda表达式 FutureTask<Integer> ft = new FutureTask<>(()->{ int i = 0; for (; i < 10; i++) { Thread thread = Thread.currentThread(); System.out.println(i + " " + thread.getName()); } return i; }); Thread thread = new Thread(ft); thread.start(); //System.out.println("返回值:" + ft.get()); System.out.println("==============="); Thread thread1 = Thread.currentThread(); System.out.println("线程:" + thread1.getName()); } }
执行结果:
===============
0 Thread-0
1 Thread-0
2 Thread-0
3 Thread-0
4 Thread-0
5 Thread-0
6 Thread-0
7 Thread-0
线程:main
8 Thread-0
9 Thread-0
当把“System.out.println("返回值:" + ft.get());”注释掉以后,两个线程就可以并发执行了。
2.4、Future设置超时时间
String result; ExecutorService executor = Executors.newSingleThreadExecutor(); FutureTask<String> future = new FutureTask<>(new Callable<String>() { public String call() throws InterruptedException { TimeUnit.SECONDS.sleep(4); return "ok"; } }); executor.execute(future); //在这里可以做别的任何事情 try { result = future.get(5000, TimeUnit.MILLISECONDS); //取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果。 System.out.println(result); } catch (InterruptedException e) { future.cancel(true); } catch (ExecutionException e) { future.cancel(true); } catch (TimeoutException e) { future.cancel(true); } finally { executor.shutdown(); }
设置超时时间,如果超过5秒还没有返回值,则抛出超时异常