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