Java基础:多线程

来源:互联网 发布:阿里云分销平台 编辑:程序博客网 时间:2024/06/04 18:14

线程:

线程(Thread)与进程(Process)不同之处在于 ,进程有PCB(Process Control Block),有独立的地址、内存分配,空间代价可以很大也可以很小,而线程只能在PCB里面存在。


多线程:

定义:

所谓多线程,就是在同一个进程中,让它的多个线程并发,以达到一种所有线程“看起来”都同时运行的状态,而不需要每个线程都等待前一个线程彻底执行完任务才能轮到自己来执行,极大地提高了CPU的利用率。要知道,常规的程序进程,如果不采用多线程,对CPU来说,“一个能打的都没有”。

原理:

简单来说,多线程的关键原理其实一句话就可以概括:时间片轮转

具体来说,线程在并发的时候,进程将时间分成了很多个时间片,这些时间片往往十分微小,比如若干毫秒,取决于操作系统的线程切换算法。于是线程按这个切换算法在“线程流水”上排好队,轮流领取时间片来执行自己的工作。

所以,多线程实质是“假象”,只是它们并发时的时间间距让人类无法察觉,使人感觉像是在同时运行,而CPU在忙于执行每个线程的任务的同时还在不停地切换线程。

在java中,每一个进程都有一个主线程,用来生成其他所有子线程。在单线程的方案下,主线程的地位相当于这进程。主线程在最后完成执行,用以执行各种关闭。


定义线程类的方法:

1.实现Runnable接口:

实现接口的run()方法。

public class MyThread implements Runnable{public void run() {//what this thread does}}

2.继承Thread类:

重写父类的run()方法。

public class MyThread extends Thread{@Overridepublic void run(){//what this thread does}}

区别:

1.Runnable在资源共享时更加稳定。

2.Runnable作为接口能弥补多继承的限制。

3.访问当前线程时Thread更方便,直接使用this,而Runnable需使用Thread.currentThread();。

4.Runnable启动线程较为麻烦,详见下文。

run方法:

值得注意的是,不能直接调用run方法,否则线程将直接调用,这样就不是线程并发了。应该调用start方法来启动线程。

//mainMyThread t = new MyThread();t.start();

线程状态:

状态机:

Born->Ready->Running

Running->Waiting->Ready

Running->Sleeping->Ready

Running->Blocked->Ready

Running->Suspended->Ready

Running->Dead->Ready

状态:

1.新建:

(Born)

构造了线程类对象。

2.就绪:

(Ready)

线程启动,进入队列排队,由start()方法启动。

3.运行:

(Running)

位于线程队列上,自动调用run()方法。

4.堵塞:

(Blocked / Suspended / Waiting / Sleeping)

不位于线程队列上,由sleep(),suspend(),wait()等引起。

5.死亡:

(Dead)
由stop()或run()方法执行结束。

线程优先级:

void setPriority(int p);int getPriority();NORM_PRIORITY = 5;MAX_PRIORITY=10;//默认MIN_PRIORITY=1;

线程同步:

产生:

当多个线程共享访问同一个资源(如某个静态变量)时,可能产生问题。

定义:

保证同一资源在同一时间点只被一个线程占用。

方案:

监视器,也可以叫做互斥锁、同步锁,简称锁。

同步方法:

同一时刻只能有一个线程进入,其他要求进入的线程等待,保证每个线程在调用这个方法时,独占其资源。

synchronized void methodA(){}

同步块:

可放在同步方法中,

synchronized(线程类对象){}

死锁:

由同步块有可能产生死锁
线程死锁的原理比较简单,就是A线程占用了a资源,需要b资源,而B线程占用了b资源,需要a资源,在同一时刻互不相让,一直死等。
在java中,产生过程如下:
1.A线程类的同步方法或同步块被调用。
2.B线程类的同步方法或同步块并发。
3.A线程类的同步方法或同步块尝试调用B线程类的同步方法。
4.死锁产生。

线程通信:

对于死锁,一个行之有效的解决手段就是线程通信

方法:

wait(); //直到其他线程调用这个同步方法,并调用notify()方法,否则将一直等待。notify(); //开锁。唤醒等待的线程。notifyAll(); //让出控制权,让优先级最高的线程占领资源

实例:

public class ChopStick{boolean available;ChopStick(){available=true;}public synchronized void takeUp(){while(!available){try{wait();}catch(InterruptedException e){}available=false;}public synchronized void putDown(){available=true;notify();}}

重要线程方法:

线程名:

static String getName();static void setName(String name);

启动线程:

继承自Thread:

public class MyThread extends Thread{@overridepublic void run(){...}}public static void main(String[] args){MyThread t = new MyThread();t.start();}

实现了Runnable:

public class MyThread implements Runnable{@overridepublic void run(){...}}public static void main(String[] args){MyThread _t = new MyThread();Thread t = new Thread(_t); //需“套”在一个Thread对象上才能使用start方法t.start();}

线程生死:

boolean isAlive(); //start()前,stop()后,main结束前为false,其余均为true

生存线程:

public static int aliveCount(); //当前活动的线程数

当前线程:

public static Thread currentThread(); //当前时间片中运行的线程

精灵/守护线程:

所谓精灵线程就是等所有其他线程都执行完后再执行的线程,比如主线程。
void setDaemon(boolean on);boolean isDaemon();

强制运行/线程归并:

所调线程内容并入当前线程,直到执行完才继续执行当前线程后面的内容。
void join();

这个概念过于抽象,下面拿一个赛车的例子来解释join的用法,假设主线程为赛车A,子线程MyThread为赛车B,赛车A跑到一半时车抛锚了,最后才修好:

// MyThread.java,子线程类public class MyThread extends Thread {public void run() {// 赛车B起跑for (int i = 1; i < 10; i++)System.out.println("Car B running, 0." + (10 - i)+ "kms to the final !"); // 赛车B离终点越来越近System.out.println("Car B finished the race !"); // 完成比赛}}// Test.java,主线程mainpublic class Test {public static void main(String[] args) {// 赛车A准备完毕MyThread t = new MyThread();t.start(); // 赛车B起跑// 赛车A起跑for (int i = 1; i < 10; i++) {// 赛车A行至一半车抛锚了if (i == 5)try {// 赛车B超过了赛车A(如果之前没有超过的话),继续冲向终点。当赛车B跑完全程后,赛车A才修好继续跑t.join();} catch (InterruptedException e) {e.printStackTrace();}// 赛车A离终点越来越近System.out.println("Car A running, 0." + (10 - i)+ "kms to the final !");}// 赛车A修好了车,终于完成了比赛System.out.println("Car A finished the race !");}}

线程暂停:

休眠:

线程休眠一段指定的时间,进入blocked堵塞状态,但不交出锁(监视器、同步资源)的控制权。
static void sleep(long mills);

让权:

线程自身调用,当前线程让出当前所占的时间片,并立即进入轮转队列,在下一个时间片的竞争中参与竞争。
不交出锁(监视器、同步资源)的控制权。
static void yield();

等待及唤醒:

只能出现在同步块或同步方法中,或者说只能在锁里调用。
调用wait方法的线程会进入等待状态,等待一段时间或被其他线程唤醒后继续执行,并交出锁(监视器、同步资源)的控制权。
void wait(); //持续等待状态,除非当其他线程调用notify或notifyAll方法void wait(long mills); //持续等待状态,除非当其他线程notify或notifyAll,或当过去了参数里的那么长的时间

notify与notifyAll:

void notify();void notifyAll();
二者都只能出现在同步块或同步方法中。
notifyAll方法,唤醒全部处于等待状态的线程,然后让他们公平地竞争同步锁。
notify方法,会选择一个处于等待状态的线程(比如:优先级最高的线程)来唤醒,然后让其获得锁的控制权。同时,不惊动其他处于等待状态的线程。所有其他的线程均处于等待状态,即便锁已经失去了控制者,也不会去竞争,除非被notifyAll,或者除了被notify选择的那一个线程。
其实很好理解,notifyAll类似于比武招亲,notify就是私奔。

挂起与恢复:

调用suspend方法的线程会进入挂起状态,将一直挂起,除非被resume恢复,但不交出锁(监视器、同步资源)的控制权。
void suspend(); //持续挂起状态,除非调用resume方法void resume(); //解除挂起状态,继续运行

比较:

或许下面的表格能更直观地表示这些能使线程暂停的方法之间的区别:
线程暂停方法对比方法名恢复条件恢复方法名锁的控制权sleep(long mills);a.时间到,b.被中断interrupt();保持yield();a.交出当前时间片后立即进入轮转队列-保持wait();a.其他线程调用notify();或notifyAll();,b.被中断notify();notifyAll();interupt();让出wait(long mills);a.时间到,b.其他线程调用notify();或notifyAll();,c.被中断notify();notifyAll();interupt();让出suspend();a.其他线程调用本线程实例的resume();方法resume();保持

中断:

void interrupt();

一般而言,interrupt并不是用来停止、暂停某个线程的正常运行,对于正在运行的线程它仅仅起到一个标记的作用,而常用来中断其线程暂停或线程归并操作(sleep,wait,join)。

中断sleep:

//MyThread.javapublic class MyThread implements Runnable {public void run() {try {Thread.currentThread().sleep(10000);} catch (InterruptedException e) {System.out.println("Sleep Interrupted!");}}}//Test.javapublic class Test {public static void main(String[] args){MyThread t_ = new MyThread();Thread t = new Thread(t_);t.start();t.interrupt();}}

中断wait:

//MyThread.javapublic class MyThread extends Thread {public void run() {//wait方法必须在同步锁里调用synchronized (this) {try {this.wait();} catch (InterruptedException e) {System.out.println("Wait Interrupted!");}}}}//Test.javapublic class Test {public static void main(String[] args){MyThread t = new MyThread();t.start();t.interrupt();}}

中断join:

同样,interrupt可以中断join,这里就不赘述了。

另外还有一个方法:

boolean isInterrupted(); //检查指定线程是否被中断,并将interrupted标志置为false;
原创粉丝点击