多线程对于有一定开发经验的程序员来说肯定不会陌生,不过相信很多人跟我一样,平时其实没有那么多的多线程实例去做,即使有可以直接使用spring来实现简单的使用,更多的是在面试中,一般都会涉及,特别是大公司的面试。
在阿里的初面中,我就被刷下来,失落感是显而易见的,虽然面试官也坦诚,如果我去了阿里也是做应用,面试问的这些东西也不会有太多涉及,面试门槛高,所以为了去大公司也得耐心的了解。当然面试官也说了,如果你知道这些偏僻的内容,对工作某个点是有用的,我同意这个说法。
所以,对于多线程这部分耐心看下来,对于找工作或者技术提升都是有用的。与君共勉!
文章借鉴于《Java多线程编程实战指南(设计模式篇)》 黄文海/著,以及极客学院多线程视频http://search.jikexueyuan.com/course/?q=%E5%A4%9A%E7%BA%BF%E7%A8%8B。
一,为什么要有多线程?
多线程的前提是各种设备有多个cpu,这样多个cpu能够并行的进行计算。
好处是:
1,吞吐量变大(即能够同时接收多个请求)
2,提高响应性(一个线程比较慢时,其他线程可以继续工作)
3,充分利用cpu资源。可以简单的理解,就是以前一个人做的事情,可以多个人独立分担去做了。
由于是多个线程独立的去做,那么肯定也会产生一些问题。
1,线程安全的问题:多个线程共享数据时,如果没有做相应的措施,那么就会产生数据一致性的问题,如读取脏数据(过期数据),丢失更新(某些线程做的更新被其他线程做的更新覆盖)
2,线程生命特征问题:单线程时一个线程串行执行不会产生问题,但是多个线程就会产生调度的问题,包括死锁,活锁(由于资源有限,调度问题,某个线程一直得不到资源)
3,上下文切换开销:一个线程的状态变更,调度停止线程的执行时,会保存该线程的上下文,即当时的环境状态,当重新执行时,需要恢复当时的环境状态,这个过程会产生开销。
4,可靠性 :线程之间是独立的,一方面一个线程意外终止了不会影响其他线程。另外一方面由于线程共享同一个进程中的资源,如果一个线程内存泄漏导致jvm崩溃停止,那么其他的线程也会停止。
通俗的来说:
有一个土堆要挖土,单线程的情况就是1个挖土机自己慢慢的挖,多线程就是10个挖土机同时来挖,好处就是快,坏处就是10个机器就会有调度的问题,保证不会抢夺资源、摩擦等问题,需要合理的处理调度。
二,线程的状态:
NEW:一个线程刚被创建,还未start(),一个线程只能一次处于此状态。
RUNNABLE:包括READY和RUNNING,线程执行start()之后就是READY状态,此时等待调度系统,被调用中就是RUNNING状态。如果执行yield()方法或者调度系统的原因,RUNNING状态也会变成READY状态,两者是能相互转化的。
BLOCKED:阻塞状态,即线程请求资源被其他线程调用,当其他资源使用完成之后,线程可以变为RUNNABLE状态。
WAITTING:无限制的等待其他线程先执行,才能执行。和BLOCKED的区别在于BLOCKED是线程获得不了资源被动的等待,WAITTING是主动的被调用者等待,相同点是需要等待别人执行完成才能执行。Object.wait() Thread.join() LockSupport.park()执行时会编程WAITTING状态,调用Object.notify() Object.notifyAll() LockSupport.unpark()能够编程RUNNABLE状态。
TIME_WAITTING:有闲时间的等待,比如等待1秒。
TERMINATED:终止状态,正常执行结束,或者跑出异常之后,都会编程这个状态。
总而言之:一个线程一定有一个NEW TERMINATED状态,其他状态可能都会有相互转化。(状态图见附件)
三,线程的实现
我们对线程的操作,基本基于对java.lang.Thread类来操作,Thread也是实现了Runnable接口。
线程实现 ① 实现Runnable接口,实现run方法,Thread类有一个传入Runnable实现的构造器,获得Thread ② 集成Thread类,重写run方法,获得Thread。
public class Instance { public static void main(String[] args) { Thread t1 = new Thread(new MyRunnables()); t1.start(); Thread t2 = new MyThreads(); t2.start(); } } class MyRunnables implements Runnable{ public void run() { System.out.println(Thread.currentThread().getName()); } } class MyThreads extends Thread{ public void run() { System.out.println(Thread.currentThread().getName()); } }
四,线程常用方法
1,t.start()启动
2,t.isAlive()启动前是false,启动后是true
3,Thread.currentThread()获得当前线程
4,Thread.sleep(1000)睡眠
5,t.join()强行执行
6,t.yield()礼让
五,线程同步
多个线程占用相同的资源,为了避免脏数据的问题。
public class Synchronization { public static void main(String[] args) { MyRunable r = new MyRunable(); Thread t1 = new Thread(r,"窗口1"); Thread t2 = new Thread(r,"窗口2"); Thread t3 = new Thread(r,"窗口3"); t1.start(); t2.start(); t3.start(); } } class MyRunable implements Runnable{ private Integer ticket = 5; public void run() { synchronized (this){ while (ticket > 0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖出一张,剩余车票:" + (--ticket)); } } } }
打印结果:
窗口1卖出一张,剩余车票:4 窗口3卖出一张,剩余车票:3 窗口2卖出一张,剩余车票:2 窗口3卖出一张,剩余车票:1 窗口1卖出一张,剩余车票:0
对比没有家synchronized关键字结果:
窗口2卖出一张,剩余车票:4 窗口3卖出一张,剩余车票:3 窗口1卖出一张,剩余车票:2 窗口2卖出一张,剩余车票:1 窗口3卖出一张,剩余车票:0 窗口1卖出一张,剩余车票:-1 窗口2卖出一张,剩余车票:-2
对共享的变量或者方法上加上synchronized,也可以在代码块上加上这个关键字。
六,线程优先级
t1.setPriority(Thread.MAX_PRIORITY); t1.setPriority(Thread.NORM_PRIORITY); t1.setPriority(Thread.MIN_PRIORITY);
目前理解不深,之后补充。