进程是所有线程的集合,每一个线程是进程中的一条执行路径。
比方:通过查看 windows 任务管理器中的列表,我们可以把运行在内存中的 exe 文件理解成进程,进程是受操作系统管理的基本运行单元。
主要体现在多线程提高程序效率,但是需要注意,并不是使用了多线程就一定能提升性能,有的情况反而会降低性能。
多线程应用场景:
我们知道,在我们单线程中,代码是顺序执行的,如果前面的操作发生了阻塞,那么就会影响到后面的操作,这时候可以采用多线程,可以理解成异步调用;其实前端里的 ajax 就是一个很好地例子,默认 ajax 是开启异步的,调用时浏览器会启一个新的线程,不阻塞当前页面的正常操作;
以http server为例,如果只用单线程响应HTTP请求,即处理完一条请求,再处理下一条请求的话,CPU会存在大量的闲置时间;
因为处理一条请求,经常涉及到RPC、数据库访问、磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应的时候,CPU却不能去处理新的请求,因此http server的性能就很差;
所以很多web容器,都采用对每个请求创建新线程来响应的方式实现,这样在等待请求A的IO操作的等待时间里,就可以去继续处理请求B,对并发的响应性就好了很多 。
/** * author: niceyoo * blog: https://cnblogs.com/niceyoo * desc: */ public class ThreadDemo { public static void main(String[] args) { System.out.println("-----多线程创建开始-----"); /* 1.创建一个线程*/ CreateThread createThread = new CreateThread(); /* 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法*/ System.out.println("-----多线程创建启动-----"); createThread.start(); System.out.println("-----多线程创建结束-----"); } } class CreateThread extends Thread { /*run方法中编写 多线程需要执行的代码*/ @Override public void run() { for (int i = 0; i< 10; i++) { System.out.println("i:" + i); } } }
打印结果:
-----多线程创建开始----- -----多线程创建启动----- -----多线程创建结束----- i:0 i:1 i:2 i:3 i:4 i:5 i:6 i:7 i:8 i:9
线程调用 start() 方法后,代码并没有从上往下执行,而是有一条新的执行分支。
注意:画图演示多线程不同执行路径。
/** * author: niceyoo * blog: https://cnblogs.com/niceyoo * desc: */ class CreateRunnable implements Runnable { @Override public void run() { for (int i = 0; i< 10; i++) { System.out.println("i:" + i); } } } public class ThreadDemo2 { public static void main(String[] args) { System.out.println("-----多线程创建开始-----"); /* 1.创建一个线程 */ CreateRunnable createThread = new CreateRunnable(); /* 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法 */ System.out.println("-----多线程创建启动-----"); Thread thread = new Thread(createThread); thread.start(); System.out.println("-----多线程创建结束-----"); } }
打印结果:
-----多线程创建开始----- -----多线程创建启动----- -----多线程创建结束----- i:0 i:1 i:2 i:3 i:4 i:5 i:6 i:7 i:8 i:9
使用实现Runnable接口好,继承方式的扩展性不强,java总只支持单继承,如果一个类继承Thread就不能继承其他的类了。
java 中有两种线程,一种是用户线程,一种是守护线程。
用户线程:指用户自定义创建的线程,主线程停止,用户线程不会停止。
守护线程:当前进程不存在或主线程停止,守护进程也会被停止。
只需要调用 setDaemon(true) 方法即可设置为守护线程。
/** * author: niceyoo * blog: https://cnblogs.com/niceyoo * desc: */ public class DaemonThread { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(100); } catch (Exception e) { } System.out.println("我是子线程..."); } } }); thread.setDaemon(true); thread.start(); for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (Exception e) { } System.out.println("我是主线程"); } System.out.println("主线程执行完毕!"); } }
运行结果:
... 我是主线程 我是子线程... 我是主线程 主线程执行完毕!
从运行结果看到,main函数执行完了,守护线程也跟着停止了。
线程从创建、运行到结束,总是处于下面五个状态之一:
新建状态、就绪状态、运行状态、阻塞状态以及死亡状态。
当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
线程运行过程中,可能由于各种原因进入阻塞状态:
有两个原因会导致线程死亡:
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用 isAlive() 方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
在多线程中也是有执行的优先级的,所谓的优先级,就是cpu是否格外关注这位小兄弟,优先级越大,自然获得的好处就越多。
当在主线程当中执行到 小弟.join() 方法时,就认为主线程应该把执行权让给 小弟。
举一个例子:
创建一个线程,如何让子线程执行完毕后,主线程才能执行呢?
public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + "i:" + i); } } }); t1.start(); /* 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1 */ t1.join(); for (int i = 0; i < 10; i++) { try { Thread.sleep(10); } catch (Exception e) { } System.out.println("main" + "i:" + i); } } }
打印结果:
Thread-0i:0 Thread-0i:1 Thread-0i:2 Thread-0i:3 Thread-0i:4 Thread-0i:5 Thread-0i:6 Thread-0i:7 Thread-0i:8 Thread-0i:9 maini:0 maini:1 maini:2 maini:3 maini:4 maini:5 maini:6 maini:7 maini:8 maini:9
虽然上边在介绍 join 方法时提到了优先级,但是在使用 join() 方法后,该线程却变成了完全主导,这或许并不是你想要的结果。
现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority的一些量和方法。
class PrioritytThread implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().toString() + "---i:" + i); } } } public class ThreadDemo4 { public static void main(String[] args) { PrioritytThread prioritytThread = new PrioritytThread(); Thread t1 = new Thread(prioritytThread); Thread t2 = new Thread(prioritytThread); t1.start(); /* 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配 */ t1.setPriority(10); t2.start(); } }
打印结果:
Thread[t1,10,main]---i:0 Thread[t1,10,main]---i:1 Thread[t1,10,main]---i:2 Thread[t1,10,main]---i:3 Thread[t1,10,main]---i:4 Thread[t1,10,main]---i:5 Thread[t1,10,main]---i:6 Thread[t1,10,main]---i:7 Thread[t1,10,main]---i:8 Thread[t1,10,main]---i:9 Thread[t2,5,main]---i:0 Thread[t2,5,main]---i:1 Thread[t2,5,main]---i:2 Thread[t2,5,main]---i:3 Thread[t2,5,main]---i:4 Thread[t2,5,main]---i:5 Thread[t2,5,main]---i:6 Thread[t2,5,main]---i:7 Thread[t2,5,main]---i:8 Thread[t2,5,main]---i:9
答:进程是所有线程的集合,每一个线程是进程中的一条执行路径。
答:提高程序效率
答:继承Thread或Runnable 接口。
答:实现Runnable接口好,继承方式的扩展性不强,java总只支持单继承,如果一个类继承Thread就不能继承其他的类了。
答:主要能体现到多线程提高程序效率。
举例:分批发送短信。
我们了解了什么是线程,线程是一条执行路径,每个线程互不影响;
了解了什么是多线程,多线程在一个线程中,有多条不同的执行路径,并行执行,目的为了提高程序效率。
了解了线程创建常见的两种方式:继承Thread类实现run方法,或者实现Runnable接口。
事实上,实际开发中这两种方式并不常见,而是使用线程池来进行管理。
了解了线程的几种状态,新建、就绪、运行、阻塞、死亡。
了解了线程里面也是有优先级的,用数值1-10来记录,默认是5,最大是10,通过调用 setPriority(10) 来设置优先级,需要一提的是,并不是优先级越大就一定要先执行完,只是优先执行完的概率要大。
我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下微信公众号哈:niceyoo