概述
程序:Program,是一个静态的概念
进程:Process,是一个动态的概念
进程是程序的一次动态执行过程, 占用特定的地址空间。每个进程都是独立的,由3部分组成cpu,data,code
缺点:内存的浪费,cpu的负担
线程:Thread,是进程中一个“单一的连续控制流程” (a single sequential flow ofcontrol)/执行路径。线程又被称为轻量级进程(lightweight process)。
一个进程可拥有多个并行的(concurrent)线程。一个进程中的线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象、通信、数据交换、同步操作。由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
实现
在Java中负责线程的这个功能的是Java.lang.Thread 这个类,可以通过创建 Thread 的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。通过调用Thead类的start()方法来启动一个线程。
继承Thread类方式的缺点:那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类
通过Runnable接口实现多线程。优点:可以同时实现继承。实现Runnable接口方式要通用一些。
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
使用下述线方法获得或设置线程对象的优先级。
intgetPriority();
voidsetPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
Demo
public static void main(String[] args) { Thread thread1 = new Thread(new TestThread2(1)); Thread thread2 = new Thread(new TestThread2(2)); t1.setPriority(1); t2.setPriority(10); thread1.start(); thread2.start(); } public void run() { for(int i=0;i<100;i++){ System.out.println(this.threadId+":"+i); } }
状态
新生状态:
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)
就绪状态:
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态:
在运行状态的线程执行自己的run方法中代码,直到调用其他方法而终止、或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
阻塞状态:
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
死亡状态:
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop或destroy方法来终止一个线程。不推荐使用这两个方法,前者会产生异常,后者是强制终止,不会释放锁。
同步与死锁
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。
所谓死锁,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。很显然,如果没有外力的作用,那么死锁涉及到的各个进程都将永远处于封锁状态。从上面的例子可以看出,计算机系统产生死锁的根本原因就是资源有限且操作不当。即:一种原因是系统提供的资源太少了,远不能满足并发进程对资源的需求。这种竞争资源引起的死锁是我们要讨论的核心。例如:消息是一种临时性资源。某一时刻,进程A等待进程B发来的消息,进程B等待进程C发来的消息,而进程C又等待进程A发来的消息。消息未到,A,B,C三个进程均无法向前推进,也会发生进程通信上的死锁。另一种原因是由于进程推进顺序不合适引发的死锁。资源少也未必一定产生死锁。就如同两个人过独木桥,如果两个人都要先过,在独木桥上僵持不肯后退,必然会应竞争资源产生死锁;但是,如果两个人上桥前先看一看有无对方的人在桥上,当无对方的人在桥上时自己才上桥,那麽问题就解决了。所以,如果程序设计得不合理,造成进程推进的顺序不当,也会出现死锁。
死锁的恢复
一旦在死锁检测时发现了死锁,就要消除死锁,使系统从死锁状态中恢复过来。
(1)最简单,最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程。
(2)撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素。
此外,还有进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。
任务调度
Timer定时器类
TimerTask任务类
通过java timer timetask:(spring的任务调度就是通过他们来实现的)
在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的。而TimerTask类是一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力。
在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
业务思想
多线程机制操作,独立指令流并发执行,使得用户很轻松。
在此讲述了 Java 多线程编程的方方面面,包括创建线程,以及对多个线程进行调度、管理。深刻认识到了多线程编程的复杂性,以及线程切换开销带来的多线程程序的低效性,这也促使我们认真地思考一个问题:我们是否需要多线程?何时需要多线程?
多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是它的内在特点。
假如我们的程序根本不要求多个代码块并发执行,那自然不需要使用多线程;假如我们的程序虽然要求多个代码块并发执行,但是却不要求乱序,则我们完全可以用一个循环来简单高效地实现,也不需要使用多线程;只有当它完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。