进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:线程是指进程内的一个执行单元,也是进程内的可调度实体.
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程与进程的区别?
地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进 程有自己独立的地址空间
资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
线程是处理器调度的基本单位,但进程不是.
二者均可并发执行.
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start() 方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使 用权。即可以开始争用虚拟机。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时 停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分 三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步:运行的线程在获取对象的同步锁时,若该同步锁被别的线程 占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,则则JVM会把该线程置为阻塞状态。当sleep()状态超时、join() 等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结 束生命周期。
阻塞态不可直接进入运行态,但是运行态可能直接进入就绪态(如在时间片轮转调度时)
创建线程的两种方式:
继承Thread类、实现Runnable接口
实现Runnable接口比继承Thread类所具有的优势
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
Thread常用方法:
start()方法:
调用后并不是立即执行多线程代码,而是使得该线程变为可运/就绪行态(Runnable),什么时候运行是由操作系统决定的
sleep()方法:
使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。在指定的时间内肯定不会被执行;当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好
必须放在try catch块里,否则会抛出异常,编译不通过
使用sleep()避免死锁现象发生
是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,
yield() 方法:线程让步
暂停当前正在执行的线程对象,使当前线程重新回到可执行状态,把执行机会让给相同或者更高优先级的线程。
但是,执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
wait()方法:
是Object类里的方法
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用wait(0) 一样
wait( ),notify( )和notifyAll( )方法在对象中是用final方法实现的,所以所有的类都含有它们。
wiat()必须放在synchronized block中,否则会在program runtime时扔出java.lang. IllegalMonitorStateException异常。
join()方法
等待其他线程终止。
在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态,即使得当前线程加入另一个线程的等待队列
- 本质上是通过wait()方法实现阻塞的
- 如果子线程没有启动或者子线程执行结束,主线程(调用join方法的线程)不用等待可以继续执行
Interrupt()方法:
在线程sleep时,强行唤醒线程,并使线程从sleep的catch块开始执行
notify()方法:线程唤醒
线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争
notifyAll(),唤醒在此对象监视器上等待的所有线程
wait和sleep区别
共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出 InterruptedException。
(如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException)
不同点:
1. Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 Wait通常被用于线程间交互,sleep通常被用于暂停执行。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
所以sleep()和wait()方法的最大区别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时,释放对象锁。
但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
5.isAlive()
线程在创建态和死亡态时为false,其余为true。
不鼓励使用主线程来stop线程,因为易破坏原子操作。
主线程:JVM调用程序main()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程,比如JVM的垃圾回收线程
用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
由主线程或setDaemon()方法创建,特点:前台线程结束了后台线程会自动结束(死亡)。
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。前后台线程使用时间片轮转调度策略。
线程同步
1、synchronized关键字的作用域有三种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
3)除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
1.对象锁:
锁的是类里的一个语句块
2.方法锁:
锁的是包含该方法的对象,即所有包含该方法的对象都不能访问被锁方法
3.类锁(静态锁)
锁的是加载该类时创建的反射对象,即所有该类的静态方法
synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
2、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
中断线程的三种方式:
1.共享flag变量,主线程通过和次线程共享一个flag来终止次线程,主线程通过修改flag来停止次线程。
2.使用stop()终止次线程,但是不推荐,如果一个线程在锁的代码中可能会破坏锁的原子性(还未执行完线程代码就被中断)。
3.强行从运行态转变为就绪态,次线程睡眠后,当主线程执行interrupt()方法强行唤醒次线程让其进入就绪状态,并从此线程的catch异常代码段开始执行。
Volatile:
在多线程中是用来同步变量的。线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。
volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A) 变量。
一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。
volatile是变量修饰符,而synchronized则作用于一段代码或方法。
Thread dumps:
调用后出来的信息包含线程;线程的运行状态、标识和调用的堆栈;调用的堆栈包含完整的类名,所执行的方法,如果可能的话还有源代码的行数。
synchronized和lock的区别:
Lock 的锁定是通过代码实现的,而synchronized 是在JVM 层面上实现的。
synchronized 在锁定时如果方法块抛出异常,JVM会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock的话就享受不到 JVM带来自动的功能,出现异常时必须在 finally将锁释放掉,否则将会引起死锁。
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。