一,线程和进程的区别:
进程:每一个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包括1--n个线程
线程:同一类线程共享代码和数据空间,每一个线程有独立的执行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:创建、就绪、执行、堵塞、终止
多进程是指操作系统能同一时候执行多个任务(程序)
多线程是指在同一程序中有多个顺序流在执行
实现多线程的方法:
1.继承Thread类
2.实现Runnable接口
并与Future、线程池结合使用
二、扩展java.lang.Thread类
继承Thread类,重写run方法,然后调用start()方法,start方法反复调用的话,会出现java.lang.IllegalThreadStateException异常
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=mTh1;
mTh1.start();
mTh2.start();
package com.multithread.learning;
/**
* 第一种多线程方式:继承Thread类
*/
public class Main {
/**
* 程序启动执行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建
*/
public static void main(String[] args) {
Thread1 mTh1 = new Thread1("A");
Thread1 mTh2 = new Thread1("B");
//start()方法的调用后并非马上执行多线程代码。而是使得该线程变为可执行态(Runnable),什么时候执行是由操作系统决定的
mTh1.start();
//可是start方法反复调用的话,会出现java.lang.IllegalThreadStateException异常
mTh2.start();
}
}
class Thread1 extends Thread {
private String name;
public Thread1(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " 执行 :" + i);
try {
sleep((int) Math.random() * 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
三、实现java.lang.Runnable接口
package com.multithread.runnable;
import static java.lang.Thread.sleep;
public class Main {
public static void main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread(new Thread2("D")).start();
}
}
class Thread2 implements Runnable {
private String name;
public Thread2(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "执行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
四、Thread和Runnable的差别
假设一个类继承Thread。则不适合资源共享。可是假设实现了Runable接口的话,则非常easy的实现资源共享
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个同样的程序代码的线程去处理同一个资源
2):能够避免java中的单继承的限制
3):添加程序的健壮性,代码能够被多个线程共享,代码和数据独立
4):线程池仅仅能放入实现Runable或callable类线程,不能直接放入继承Thread的类
提醒一下大家:main方法事实上也是一个线程。在java中所以的线程都是同一时候启动的,至于什么时候。哪个先执行。全然看谁先得到CPU的资源。
在java中,每次程序执行至少启动2个线程。一个是main线程,一个是垃圾收集线程。
五、线程状态转换
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。
3、执行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。堵塞的情况分三种:
(一)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步堵塞:执行的线程在获取对象的同步锁时。若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。
(三)、其它堵塞:执行的线程执行sleep()或join()方法。或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时。线程又一次转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
六、线程调度
1.调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的执行机会
Java线程的优先级用整数表示,取值范围是1~10。Thread类有以下三个静态常量:
static int MAX_PRIORITY 线程能够具有的最高优先级,取值为10。
static int MIN_PRIORITY 线程能够具有的最低优先级,取值为1。
static int NORM_PRIORITY 分配给线程的默认优先级。取值为5。
Thread类的setPriority()和getPriority()方法分别用来色织和获取线程的优先级,每一个线程都有一个默认的优先级,主线程的默认优先级为Thread.NORM_PRIORITY;线程的优先级有继承关系,比方A线程中创建了B线程。那么B将和A具有同样的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能非常好的映射。假设希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级採用了同样的调度方式
2.线程睡眠:Thread.sleep(long millis)方法,使线程转到堵塞状态,单位ms,当睡眠结束之后,就转为就绪(runnable)状态,sleep()平台移植性好
3.线程等待:Object类中的wait()方法,导致当前的线程等待,直到其它线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
4.线程让步:Thread.yield() 方法。暂停当前正在执行的线程对象。把执行机会让给同样或者更高优先级的线程。
5.线程添加:join()方法。等待其它线程终止。
在当前线程中调用还有一个线程的join()方法,则当前线程转入堵塞状态。直到还有一个进程执行结束,当前线程再由堵塞转为就绪状态
6.线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。假设全部线程都在此对象上等待。则会选择唤醒当中一个线程。选择是随意性的。并在对实现做出决定时发生。线程通过调用当中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定。才干继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其它全部线程进行竞争;比如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。相似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的全部线程
注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。由于有死锁倾向
七、经常使用函数说明
1.join()方法[是Thread类的一个方法]:使用场景,在非常多情况下,主线程生成并起动了子线程,假设子线程里要进行大量的耗时的运算。主线程往往将于子线程之前结束,可是假设主线程处理完其它的事务后。须要用到子线程的处理结果,也就是主线程须要等待子线
程执行完毕之后再结束,这个时候就要用到join()方法了 -- mTh1和mTh2执行结束主线程才会结束
package com.multithread.join;
class Thread1 extends Thread {
private String name;
public Thread1(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程执行开始");
for (int i = 0; i < 5; i++) {
System.out.println("子线程" + name + " 执行:" + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程执行结束!");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "主线程执行开始");
Thread1 mTh1 = new Thread1("A");
Thread1 mTh2 = new Thread1("B");
mTh1.start();
mTh2.start();
try {
//等到mTh1执行终止
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//等到mTh2执行终止
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"主线程执行结束");
}
}
2.yield():暂停当前正在执行的线程对象,并执行其他线程[ Thread.yield()方法作用是:暂停当前正在执行的线程对象。并执行其它线程]
yield()应该做的是让当前执行线程回到可执行状态
使用yield()的目的是让同样优先级的线程之间能适当的轮转执行。可是。实际中无法保证yield()达到让步目的。由于让步的线程还有可能被线程调度程序再次选中
结论:yield()从未导致线程转到等待/睡眠/堵塞状态。在大多数情况下,yield()将导致线程从执行状态转到可执行状态,以同意具有同样优先级的其它线程获得执行机会,但有可能没有效果【线程调用yield()方法之后,还有可能再次获取线程的执行权限】
package com.multithread.yield;
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield("张三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
class ThreadYield extends Thread {
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("" + this.getName() + "---" + i);
//当i为30时,该线程就会把CPU时间让掉,让其它或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 30) {
this.yield();
}
}
}
}
3.sleep()与yield()的区别
sleep()使当前线程进入停滞状态。所以执行sleep()的线程在指定的时间内肯定不会被执行
yield()仅仅是使当前线程又一次回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行
sleep 方法使当前执行中的线程睡眼一段时间,进入不可执行状态,这段时间的长短是由程序设定的。yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了例如以下操作:先检測当前是否有同样优先级的线程处于同可执行状态,如有。则把 CPU 的占有权交给此线程,否则,继续执行原来的线程。所以yield()方法称为“退让”,它把执行机会让给了同等优先级的其它线程
4.interrupt():不要以为它是中断某个线程。它仅仅是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出。从而结束线程,可是假设你吃掉了这个异常,那么这个线程还是不会中断的!
5.wait()
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作。从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内
从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同一时候本线程休眠。
直到有其它线程调用对象的notify()唤醒该线程。才干继续获取对象锁。并继续执行。
对应的notify()就是对对象锁的唤醒操作。但有一点须要注意的是notify()调用后,并非马上就释放对象锁的,而是在对应的synchronized(){}语句块执行结束。自己主动释放锁后。JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程。继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都能够暂停当前线程,释放CPU控制权,基本的差别在于Object.wait()在释放CPU同一时候。释放了对象锁的控制。
sleep()方法不会释放锁,wait()方法释放锁
package com.multithread.wait;
/**
* 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,
* 要求线程同一时候执行,交替打印10次ABC。
* 这个问题用Object的wait(),notify()就能够非常方便的解决
*/
public class MyThreadPrienter2 implements Runnable {
private String name;
//前一个线程的对象锁
private Object prev;
//自身的对象锁
private Object self;
public MyThreadPrienter2(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.println(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrienter2 pa = new MyThreadPrienter2("A", c, a);
MyThreadPrienter2 pb = new MyThreadPrienter2("B", a, b);
MyThreadPrienter2 pc = new MyThreadPrienter2("C", b, c);
new Thread(pa).start();
Thread.sleep(100);
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
输出结果:
ABCABCABCABCABCABCABCABCABCABC
先来解释一下其总体思路。从大的方向上来讲,该问题为三线程间的同步唤醒操作,基本的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。
为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序。所以每一个线程必须同一时候持有两个对象锁,才干继续执行。一个对象锁是prev。就是前一个线程所持有的对象锁。
还有一个就是自身对象锁。基本的思想就是,为了控制执行的顺序。必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印。之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。执行上述代码,能够发现三个线程循环打印ABC,共10次。程序执行的主要过程就是A线程最先执行,持有C,A对象锁,后释放A,C锁,唤醒B。
线程B等待A锁,再申请B锁,后打印B,再释放B。A锁。唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁。唤醒A。看起来似乎没什么问题,但假设你细致想一下,就会发现有问题,就是初始条件,三个线程依照A,B,C的顺序来启动,依照前面的思考,A唤醒B,B唤醒C,C再唤醒A。可是这样的假设依赖于JVM中线程调度、执行的顺序。
6.wait()与notify()方法的差别
相同点: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.sleep方法没有释放锁。而wait方法释放了锁,使得其它线程能够使用同步控制块或者方法
3.wait,notify和notifyAll仅仅能在同步控制方法或者同步控制块里面使用。而sleep能够在不论什么地方使用
所以sleep()和wait()方法的最大差别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时。释放对象锁。
可是wait()和sleep()都能够通过interrupt()方法打断线程的暂停状态,从而使线程立马抛出InterruptedException(但不建议使用该方法)
sleep()方法
sleep()使当前线程进入停滞状态(堵塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源。以留一定时间给其它线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁。所以当在一个Synchronized块中调用Sleep()方法是,线程尽管休眠了。可是对象的机锁并木有被释放,其它线程无法訪问这个对象(即使睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不一定会马上执行,这是由于其它线程可能正在执行并且没有被调度为放弃执行。除非此线程具有更高的优先级。
wait()方法
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中。同一时候失去(释放)了对象的机锁(临时失去机锁,wait(long timeout)超时时间到后还须要返还对象锁);其它线程能够訪问。
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronized block中。否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
线程常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 推断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的差别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续执行
setPriority(): 设置一个线程的优先级
八.线程同步