多线程

来源:互联网 发布:java 打印异常堆栈 编辑:程序博客网 时间:2024/06/11 16:02

14.1 多线程的概念

大家都知道我们的电脑在运行过程中都是基于进程、线程展开的

首先来了解下什么是线程、什么是进程?

进程:是指一种“自包容”的运行程序,有自己的地址空间
线程:是进程内部单一的一个顺序控制流

既然有进程,为什么又出现了线程呢?

操作系统运行多个进程以运行多个任务时,要在进程之间进行切换,这种切换的系统开销很大,对系统性能有很大影响。
为了提升性能,现代操作系统在多进程操作的基础上,将任务的划分下降到了程序级别,使得一个程序在同一时间内可以运行多个任务。每一个任务称为一个线程,能够在一个程序内运行多线程的程序称为多线程程序。

进程与线程之间有什么关系或者区别呢?

关系:线程存在于进程之中

 

相同与进程相同,通过多个线程完成多项任务也是通过CPU在任务之间切换实现的。

1.什么是进程?什么是线程?

进程线程都是由操作系统所体现的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性
进程和线程的区别在于:
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
多线程的意义在于,一个应用程序中有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

 

如何很好的理解什么是多线程呢?

大家都知道,现在的windows的电脑都是多程序运行的,在使用电脑过程中,我们感觉电脑好像是同时在运行所有的程序,其实不然,由于我们现在电脑的CPU的运算速度很快,在启动很多程序时,每个程序都需要消耗内存资源和占用CPU的内存,而我们的CPU每秒可以达到上亿次的计算,将上亿次的计算的结果分配给启动的程序,相当于把时间分成很多时间片,分配给不同的线程,使用所有的程序都可以运行,对于我们人来说,感觉起来就好像同时都在运行一样。 但在某个特定时间内,电脑只是在对一个程序进行运算,或者说:对于单个CPU的操作系统来说,在某一时刻实际上只可能有一个线程在运行。但是,通过线程切换任务的系统开销比进程切换小得多。通过基于线程的多任务系统比基于进程的多任务系统性能要好得多。当然双核或者多核的电脑多线程支持更好(非绝对)。

14.2 掌握线程的实现方式

14.2.1



 如何理解代码中的多线程呢?

我们先来看看以前写的单线程代码

下列代码执行流程

public class T{

    public static void main(String[] args) {

        m1();

    }

    public static  void m1() {

        m2();

        m3();

    }

    public static void m2() {}

    public static void m3() {}

}

从中我们了解到单线程只有一个主线程为main主线程

线程:代码的不同运行路径

简单的来看待多线程,就是代码中的运行的路径,有几个分支就是几个线程

进程的另一种说法:有人说进程可以执行,其实不然,进程是一个静态的概念,什么是进程,机器上的.class文件、.exe文件,这个就是进程,程序在执行时,先程序代码放在代码区中,程序并没有执行,进程只是准备开始,只是产生了进程,并没有开始运行。最根本的是进程中的main方法启动了,从最根本的讲我们的计算机上运行的都是线程。

每一个Thread对象一个新的线程:

最好采用实现接口的方式:原因是,Java是单继承,继承了Thread就不能继承其它类了

14.2.2

接下来了解下线程状态(生命周期)

 

new线程时还仅仅是对象,调用start()方法,并不是调用后就启动了,而是在就绪状态,准备好了并不是马上运行,万一CPU正在执行其它线程而是CPU选择性的选中你,开始运行你,等待一段时间,就又让你返回到就绪状态;运行完了就终止了;还有种情况就是运行中有情况发生就需要等待——阻塞状态。对于人来说,感觉像是start()后就运行了,真实情况并不是这样的。

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

线程生命周期的五种状态

1)、新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread  t1=new Thread();

2)、就绪(runnable)
线程已经被启动,此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

3)、运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

4)、堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

5)、死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行run()方法后终止

异常终止:调用stop()方法让一个线程终止运行

怎么控制线程转换和线程中的其它问题呢?

 

注意isAlive()活着是除了终止活着没有启动就是活的

优先级越高,获得CPU运算的时间越多,但不是全部占完

 

wait(),notify(),notifyAll()方法是位于Object类的方法,这三个方法调用的都是jvmnative方法。

wait()方法:持有该对象的线程处于等待,让出对象的控制权notify()方法:通知正在等待这个对象控制权的线程可以继续运行notifyAll()方法:通知所有等待这个对象控制权的线程继续运行

.解释说明

wait方法,有三个重载的方法: 
- wait() 
- wait(long time) 
- wait(long time1,int time2)

1.wait()方法

wait方法将当前线程置入休眠状态,知道被通知或中断为止,在调用wait()方法前,必须获取对象级别锁,因此它只能在同步方法和同步块调用。当调用wait()方法,当前线程释放锁,在从wait()方法返回前,线程与其他线程竞争重新获得锁。

2.notify()方法

该方法也要在同步方法和同步块中调用,在调用前必须获得对象级别锁,没有则抛出异常。 
该方法用来通知那些等待该对象的对象锁的其他线程,如果有多个线程等待,则挑选出一个其中出于wait状态的线程来发出通知,并使得它获取对象锁。(执行notify方法后,当前线程并不会马上释放该对象锁,要等到程序退出synchronized代码块后,当前线程才可以释放对象锁,处于wait状态的线程才可以获取该对象锁 

调用wait方法前线程线程2调用了notify方法线程线程2释放了该锁线程线程1获取到锁线程线程2结束

分析一下结果我们发现,首先是线程1获取了对象锁,然后执行了wait()方法,所以释放了该对象锁,线程2获取了对象锁,它调用了notify()方法,在执行完当前线程的同步块后,线程1获取到对象锁,继续执行线程1同步块中wait方法之后的代码,同时线程2也在同步块后面的代码。我们发现,线程在唤醒等待该对象的其他线程,并释放掉对象锁的时候,这时候对象出于空闲状态,如果有多个出于等待的线程,那么它们会竞争该对象锁,但是不影响没有加该对象锁的线程执行。

3.notifyAll()方法

notify()方法工作原理差不多,有一些小的差异: 
notifyAll使所有原来在该对象上wait的线程退出wait状态(但并非全部获取对象锁,它们会竞争,只会有一个线程获取该对象锁),在notifyAll线程退出当前的synchronized同步块后,才参与竞争该对象锁,如果有一个线程获取到该对象锁,它会继续执行,直到执行完同步块释放对象锁,其他的被唤醒的线程将继续竞争对象锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

  

14.2.3

 

14.3 线程同步

什么是线程同步呢?

有时两个或多个线程可能会试图同时访问一个资源
例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,在此情况下,数据可能会变得不一致
为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”

如何使用同步呢?

使用同步关键字synchronized来进行标识

什么是线程同步呢?

协同步调,按预定的先后次序进行运行。如:在银行取钱,同时两个人在柜台上和ATM上取走了钱,造成金额不正确的问题

 

 

2.常用方法

run() ;创建该类的子类时必须实现的方法

start();开启线程的方法

sleep(long millis) ;或者sleep(long millis,int nanos);睡眠并释放CPU的执行权不释放锁

wait()释放CPU的执行权,释放锁只能在synchronized中使用;还有wait(long time);wait(long timeout,int nanos);

notify();NotifyAll();

yied();让当前线程让出资源执行权,之后所有线程竞争

3.(1)结束线程:让run方法结束。而run方法中通常会定义循环结构,所以只要控制住循环即可

(2)方法----可以boolean标记的形式完成,只要在某一情况下将标记改变,让循环停止即可让线程结束

3)thread.join()//join()方法,等待该线程终止。执行join方法的线程加入执行,他线程会被冻结,等待线程执行结束其它线程才会恢复到可运行状态

4. 临界资源:多个线程间共享的数据称为临界资源

1)互斥锁

a.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

b.Java对象默认是可以被多个线程共用的,只是在需要时才启动“互斥锁”机制,成为专用对象。

c.关键字synchronized用来与对象的互斥锁联系

d.当某个对象用synchronized修饰时,表明该对象已启动“互斥锁”机制,在任一时刻只能由一个线程访问,即使该线程出现堵塞,该对象的被锁定状态也不会解除,其他线程任不能访问该对象。