一、什么是进程、线程、多线程?
进程当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。进程间通信依靠IPC资源,例如管道、套接字
线程是程序中的一个执行流,每一个线程都有本身的专有寄存器(栈指针、程序计数器等),但代码是共享的,即不一样的线程能够执行一样的函数。
多线程是指程序中包含多个执行流,即在一个程序中能够同时运行多个不一样的线程来执行不一样的任务,也就是说说容许单个程序建立多个并行执行的线程来完成各自的任务。线程间通信依靠JVM提供的API,例如wait()、notify、notifyAll等方法,线程间还能够经过共享的主内存来进行值的传递java
二、多线程的优缺点?
优势:能够提升CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU能够运行其余的线程而不是等待,提升程序相应效率。
缺点:线程也是程序,全部线程须要占用内存,线程越多占用的内存也越多。
线程须要协调和管理,因此须要CPU时间跟踪线程
线程之间对共享资源的访问会互相影响,必需要解决竞用共享资源的问题
线程太多会致使控制太复杂,最终可能形成不少Bug服务器
三、多线程必定比单线程快吗?
不必定,因为多线程会存在线程上下文切换,会致使程序执行速度变慢,但能够充分利用CPU,因此对于用户来讲,能够减小用户响应时间。
好比,尝试使用并行和串行分别执行累加的操做观察是否并行执行必定比串行更快:多线程
package com.test.demo; public class Tester { private static final long count = 1000000000; public static void bingxing() throws Exception { long startTime = System.currentTimeMillis(); //经过匿名内部类来建立线程 Thread thread1 = new Thread(new Runnable() { @Override public void run() { int a = 0; for(long i = 0; i < count; i++) { a += 1; } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { int b = 0; for(long k = 0;k < count; k++) { b+=1; } } }); //启动线程 thread1.start(); thread2.start(); //等待线程结束 thread1.join(); thread2.join(); long time = System.currentTimeMillis() - startTime ; System.out.println("并行花费时长:" + time + "ms"); } public static void chuanxing() { long startTime = System.currentTimeMillis(); int a = 0; for(long i = 0; i < count; i++) { a += i; } int b = 0; for(long k = 0;k < count; k++) { b+=k; } long time = System.currentTimeMillis() - startTime ; System.out.println("串行花费时长:" + time + "ms"); } public static void main(String[] args) throws Exception { bingxing(); chuanxing(); } }
循环次数 | 串行执行/ms | 并行执行/ms | 结果 |
1千 | 0ms | 2ms | 慢 |
1万 | 0ms | 2ms | 慢 |
10万 | 3ms | 4ms | 慢 |
100万 | 6ms | 4ms | 快 |
1000万 | 13ms | 11ms | 快 |
1亿 | 89ms | 78ms | 快 |
从测试结果看出当超过100万次循环后,并行执行的优点越加明显,不超过100万次循环时,串行执行的速率要比并行执行的速率高,缘由就是多线程有上下文切换的开销。ide
四、阻塞与非阻塞
阻塞和非阻塞一般用来形容多线程之间的相互影响
阻塞是指一个线程占用了临界区资源,那么其余全部须要这个资源的线程就不洗在这个临界区中进行等待,等待会致使线程挂起,这种状况就是阻塞
非阻塞强调没有一个线程能够妨碍其余线程执行,全部线程都会尝试不断向前执行函数
五、临界区
临界区用来表示一种公共资源或者共享资源能够被多个线程使用,可是每一次只能有一个线程使用它,一旦临界区资源被占用,其余线程想要使用这个资源,就必须等待测试
六、死锁Deadlock、饥饿starvation、活锁Livelock
死锁表示两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们将没法推动下去,此时称系统处于死锁状态,这些永远在互相等待的进程成为死锁进程
饥饿表示一个或者多个线程应为种种缘由没法得到所须要的资源,致使一直没法执行。致使饥饿的缘由多是该线程优先级过低,而高优先级的线程不但抢占它所须要的资源,致使其没法向前推动,另一种多是,某线程一直占着关键资源不放,致使其余须要这个资源的线程没法正常执行
活锁表示两个或者多个线程主动将资源释放给其余线程,致使没有一个线程能够同时拿到全部资源而正常执行。spa
七、如何避免死锁
指定获取锁的顺序线程
八、sleep()与wait()区别
sleep()是Thread类的静态方法,使当前线程睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,解除阻塞,进行可运行状态,等待CPU的到来。睡眠不释放锁。
wait()是Object的方法,必须和synchronized关键字一块儿使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。可是,只有从新占用互斥锁以后才会进入可运行状态。睡眠时,释放互斥锁。指针
九、synchronized关键字底层实现
进入时,执行monitorenter,将计数器+1,释放锁monitorexit时,计数器-1;当一个线程判断到计数器为0时,则当前锁空闲,能够占用,反之,当前线程进入等待状态。code
十、volatile关键字功能
直接与主内存产生交互,进去读写操做,保证可见性;禁止JVM进行指令重排序;能使一个非原子操做变为原子操做。好比对一个volatile型的long或者douuble变量的读写是原子
十一、ThreadLocal关键字
当使用ThreadLocal维护变量时,其为每个使用该变量的线程提供独立的变量副本,因此当每个线程均可以独立的改变本身的副本,而不会影响其余线程对应的副本
十二、线程池的了解
java.util.concurrent.ThreadPoolExcutor类就是一个线程池。客户端调用ThreadPoolExecutor.submit(Runable Task)提交任务,线程池内部维护的工做者线程的数量就是该线程池的线程池大小。
当前线程池大小:表示线程池中实际工做者线程的数量
最大线程池大小:表示线程池张容许纯在的工做者线程的数量上限
核心线程大小:表示一个不大于最大线程池大小的工做者线程数量上限
线程池有三种状态:
①、若是运行的线程小于核心线程大小,则Executor始终首选添加新的线程,而不进行排队
②、若是运行的线程等于或者多于核心线程大小,则Executor始终首选将请求加入队列,而不是添加新线程;
③、若是没法将请求加入队列,即队列已经满了,则建立新的线程,除非建立此线程超过最大线程池大小,在这种状况下,任务将会被拒绝
1三、线程池的做用
减小建立和销毁线程的次数,每一个工做线程均可以被重复利用,可执行多个任务
能够根据系统的承受能力,调整线程中工做线程的数目,防止由于消耗过多的内存,而把服务器过载
1四、建立线程的实现方式
继承Thread类,实现Runable接口,实现Callable接口
1五、run()方法和start()方法的区别
start()方法会新建一个线程并让这个线程执行run()方法;而直接调用run()方法只是做为一个普通的方法调用而已,它只是会在当前线程中,串行执行run()中的代码