首页 > 程序开发 > 软件开发 > Java >

JAVA多线程基础

2016-10-10

JAVA多线程基础

概述:学习此文章你将学习到的知识如下

1、线程的概念?

2、线程的生命周期?

3、线程的创建与启动?

4、线程的优先级?

5、线程的调度模型?

6、线程的合并?

7、线程的让步?

8、线程的休眠?

9、线程的同步?

10、线程的死锁?

1、什么是程序,进程,线程?

程序:是一个静态概念,是用某种语言编写的,符合一定语法规则并具有一定功能的一些指令的集合。程序往往由开始,处理和结束三部分组成。它的表现形式可能是一个文件,可能是一组程序的集合,它是应用程序执行的脚本。

进程:是一个动态的概念,可以理解为一段正在运行的程序。它是已经开始执行,但尚未结束的一种程序状态,是程序一次执行过程。一个程序中可以包含多个进程,每个进程都有自己独立的的一块内存空间和一组系统资源,即使同类进程之间也不会共享系统资源。事实上我们所说的多任务是指在一个系统中可以运行多个程序,即有多个独立运行的任务,每一个任务对应一个进程。

由于CPU同时只能执行一条系统指令,我们所看到的多个独立运行的程序实际上任务的交替运行,由于时间间隔短,所以宏观上是多个程序在同时运行。

线程:线程是比进程更小的运行单位,是程序中单个顺序的流控制,一个进程中可以包含多个线程。通俗的讲:线程就是程序中的不同执行路径。这样的话一个进程就可以为用户同时做多件事情了,例如:一个IE浏览器,它在下载图片的同时,也能供你浏览已经下载下来的页面部分,这就是通过线程机制为用户服务的典型例子。

进程和线程都是一个程序执行序列,他们的主要区别是:进程是一个实体,各个进程之间相互独立,彼此除了可利用某些通信渠道进行信息交流和通过操作系统产生交互外,各进程之间不需要知道其他进程是否存在。线程是一个任务中动态执行的一个代码段,各个线程之间可能相互影响,彼此间不一定独立。进程有自己专用的数据,多个线程共享任务中的各项资源。

2、多进程,多线程?

多进程:就是操作系统中运行多个程序。

多线程:就是同一个应用程序中有多条执行路径。

注:JAVA中采用多线程的目的是为了最大限度的利用CPU资源。

3、线程的生命周期

当一个JAVA程序运行时,至少会生成一个线程,该主线程是main()方法开始。正如一般程序一样,一个线程仅有单一的起点,不同的是线程无法自动启动,必须通过其他程序来启动。主线程的作用:一产生子线程,二最后完成执行(执行各种关闭动作)。

同进程一样,现在也有创建,运行到消亡的过程,称为线程的生命周期。用线程的状态表明线程处在生命周期的哪个阶段。线程有创建,运行,不可运行,死亡四种状态。

4、线程的状态转化图

(1)概要图

\

(2)详细图

\

5、线程的四种状态

创建状态:创建了一个线程而没有启动它,则处于创建状态。处于线程创建状态的线程为一个空的线程对象,并没有获得应有的资源,只有启动它,系统才会为它分配资源。

有两种创建线程的方法:一种方法是通过extends Thread将一个类声明为Thread的子类,另一种方法是实现Runnable声明一个类。

可运行状态:启动处于创建状态的线程,则此线程进入可运行状态。可运行状态只能说明该线程具备了运行的条件,但可运行状态并不一定是运行状态。因为在单处理器系统中运行多线程,实际上在每个时刻之多有一个线程在运行,系统中有可能有多个线程都处于可运行状态,系统通过快速切换和调度使所有可运行的线程共享处理器,形成了宏观上的多线程并发运行。在可运行状态下,线程运行的是线程体,线程体有run()方法规定,通过重写run()方法实现线程的功能。在可运行状态下可进行多种操作,例如:调用suspend(),sleep(),wait(),stop()等方法。

不可运行状态:不可运行状态由可运行状态转换而来,当可运行状态遇到下列情形会转换为不可运行状态:(1)调用suspend()方法,(2)调用sleep()方法,(3)调用wait()方法,(4)如果一个线程是和I/O操作有关,在执行I/O操作指令时,外设的速度远远低于CPU速度而是线程受阻,此时也进入不可运行状态。

当线程处于不可运行状态还可以恢复到可运行状态,主要有下面三种途径:

(1)对于使用suspend()方法进入不可运行状态的调用resume()方法恢复

(2)对于使用wait()方法进入不可运行状态的调用notify()或notifyAll()方法恢复

(3)对于使用sleep()方法进入不可运行状态的当休眠时间到了自动恢复。由于I/O阻塞进入不可运行状态的当外设完成操作后自动恢复。

在不可运行状态可以调用stop()进入死亡状态。

死亡状态:使用interrupt()和stop()进入死亡状态。

使用上面两种方法结束线程并不是很好,而因该采用更合理的方式结束线程,为什么这么说呢?

Stop()方法虽然停止了一个正在运行的线程,然而这种方法是不安全的,API中已过时。Interrupt()方法并没有立即停止一个正在运行的线程,而是在线程受到阻塞时,抛出一个中断信号,这样就得以退出阻塞。如果线程被sleep(),wait(),join()三种方法之一阻塞时,会受到一个中断异常(InterruptedException),从而提早地终结了阻塞状态。

(1)Interrupt()中断示例

public class ThreadTest {

public static void main(String[] args) {

Thread t1 = new Thread(new MyThread());

t1.start();

try {

Thread.sleep(10000);

} catch (InterruptedException e) {

e.printStackTrace();

}

t1.interrupt();

}

}

/**

* 继承Thread创建线程

*/

class MyThread extends Thread{

public void run(){

while(true){

System.out.println("====="+new Date()+"====");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

(2)示例运行效果

\

(3)理想的中断线程的办法是设置共享变量,最后线程都将检查共享变量然后再停止,这样就有机会做结束线程前的善后工作。

public class ThreadTest {

public static void main(String[] args) {

MyThread md = new MyThread();

Thread t1 = new Thread(md);

t1.start();

try {

Thread.sleep(10000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//主线程休眠10秒,然后再执行主线程,即中断t1线程

md.shutDown();

}

}

/**

* 继承Thread创建线程

*/

class MyThread extends Thread{

boolean flag = true;

public void run(){

while(flag){

System.out.println("====="+new Date()+"====");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

return;

}

}

}

//设置开关变量

public void shutDown(){

flag = false;

}

}

(4)示例运行效果图

\

6、线程的创建

(1)通过extends Thread将一个类声明为Thread的子类

class MyThread extends Thread{

public void run(){

System.out.println("我是继承Thread类创建的");

}

}

(2)实现Runnable声明一个类

class MyRunnable implements Runnable{

@Override

public void run() {

System.out.println("我是实现Runnable接口创建的");

}

}

(3)两种创建线程方法的比较

第(1)种方法:适合用单重继承情况,不能再继承其他类。

第(2)种方法:当一个线程已继承了另一个类时,就只能用第(2)种方法来创建进程。

第(1)种方法不是很常用,一般使用第(2)种方法。

7、线程的启动

线程的启动时通过调用start()方法来启动的,启动后的线程就进入了可运行状态 ,但是同一线程不能连续启动。

(1)直接调用run()方法与start()的区别?

\

8、线程调度模型

如果同一时刻有多个线程处于可运行状态,它们要排队等待CPU资源。此时,每个线程都会得到一个系统自动分配的线程优先级,线程优先级的高低表明了线程的重要或紧急的情况。可运行状态的线程按照优先级排队,线程调度依据优先级基础上的“先到先服务”的原则。

线程调度管理器负责线程的排队和CPU在线程间的分配,并由线程调度算法进行调度。当线程调度管理器选中某个线程时,该线程获得CPU资源从而进入运行状态。

线程调度是先占式调度,即当一个线程在运行状态的时候,突然遇到一个更高优先级别的线程,这个更高级别的线程会立即进入运行状态得到CPU的资源,而之前的那个线程则进入可运行状态,排队等待CPU分配资源。先占式调度有两种方式:一分时方式,二独占方式。

独占式:此线程会一直执行下去,知道执行完毕或遇到其他情况主动放弃CPU,或被更高级别的线程抢占了CPU资源。

分时式:当前线程获得一个时间片,当时间到了,不管你执行或没执行完也要让出CPU资源,进入可运行状态等待下一个时间片的调度。

9、线程的优先级

通常情况下系统会为每个JAVA线程赋予一个介于最大和最小优先级之间的一个数,通常是1-10之间的一个数字,数字越大表示优先级越高,情况越紧急。每一个优先级都对应一个Thread类的静态公用常量。

例:public static final int MAX_PROPRITY=10;

Public static final int MIN_PROPRITY=1;

Public static final int NORMAL_PROPRITY=5;

新建线程会继承创建它的父线程的优先级。父线程指的是执行新线程的语句所在线程,它可能是主线程,也可能是另一个用户创建的线程。可以通过setPrority()/getPrority()方法来设置/获取线程的优先级。优先级只是代表得到CPU资源的概率大,并不存在绝对的优先级高就得到CPU资源。

public class ThreadTest {

public static void main(String[] args) {

System.out.println("main的优先级:"+Thread.currentThread().getPriority());

Thread t1 = new Thread(new MyThread());

Thread t2 = new Thread(new MyRunnable());

t1.start();

int a =t1.getPriority();

System.out.println("t1的优先级:"+a);

t2.start();

int b =t2.getPriority();

System.out.println("t2的优先级:"+b);

}

}

10、线程合并(join)

public class ThreadTest {

public static void main(String[] args) {

MyThread md = new MyThread();

Thread t1 = new Thread(md);

t1.start();

try {

t1.join();//线程合并

} catch (InterruptedException e) {

e.printStackTrace();

}

for(int i=0;i<10;i++){

System.out.println("我是主线程");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

return;

}

}

}

}

/**

* 继承Thread创建线程

*/

class MyThread extends Thread{

public void run(){

for(int i=0;i<10;i++){

System.out.println("我要合并到主线程");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

return;

}

}

}

}

(1)没合并运行效果

\

(2)合并运行效果

\

(3)线程B要合并到 A举例

\

11、线程礼让(yield),只礼让一次。并且也礼让过后不一定是其它线程立即使用CPU资源,也有可能该礼让的资源又重新得到CPU的资源。

public class ThreadTest {

public static void main(String[] args) {

MyThread md = new MyThread();

Thread t1 = new Thread(md);

Thread t2 = new Thread(md);

t1.setName("我是t1");

t2.setName("我是t2");

t1.start();

t2.start();

}

}

/**

* 继承Thread创建线程

*/

class MyThread extends Thread{

int i=0;

public void run(){

for(;i<100;i++){

System.out.println(currentThread().getName());

}

if(i%2 ==10){

Thread.yield();//i能整出2的时候礼让一次,礼让后并不一定会有其他线程来抢占资源

}

}

}

12、线程同步

多个线程同一时刻访问同一资源,如果线程间协调不好,极易产生数据不一致的后果。这个多个线程访问同一资源时线程之间协调的东西称之为线程的同步。一个线程在访问一个资源时,这一时刻这份资源归这个独占,其他线程不能访问。

实现线程同步:在JAVA中线程同步是通过synchronized关键字实现的。被声明为同步的方法只能被线程顺序的使用,在一个线程对该方法的使用之前,该方法使用的任何资源都是独享的,其他线程处于阻塞状态。

将方法设置为同步的方式:synchronized void myMethod(){},将方法设置为synchronized后,系统将为该方法设置一个信号量。当系统需要调用该方法时,首先检查信号量的状态。如果确定该方法没有正在被调用,则首先将信号置位,然后调用该方法。当方法执行后,将该信号量复位,以允许其他线程调用该方法。

在程序中,可能只想实现某一段代码的同步,而不是整个方法。这种情况下,可以使用如下的设置方法:synchronized(object){},若使用synchronize(this),则{}内的程序语句都会同步化。

(1)多线程同步示例:(synchronized修饰方法)

public class ThreadTest {

public static void main(String[] args) {

Share share = new Share();

MyThread md = new MyThread(share);

MyRunnable me = new MyRunnable(share);

Thread t1 = new Thread(md);

Thread t2 = new Thread(me);

t1.start();

t2.start();

}

}

/**

* 继承Thread创建线程

*/

class MyThread extends Thread{

private Share share;

public MyThread(Share share) {

super();

this.share = share;

}

public void run(){

share.display();

}

}

/**

* 实现Runnable接口创建线程

*/

class MyRunnable implements Runnable{

private Share share;

public MyRunnable(Share share) {

super();

this.share = share;

}

public void run(){

share.display();

}

}

/**

*共享资源

*/

class Share{

private int i;

public synchronizedvoid display(){

for(int j=0;j<3;j++){

i++;

System.out.println(Thread.currentThread().getName()+"\t"+i);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

(2)不同步示例运行结果

\

(3)同步示例运行结果

\

(4)多线程同步示例(synchronized修饰代码块)

class Money{

private double count;

public void saveMoney(double money){

synchronized(this){

count=count+money;

System.out.println(Thread.currentThread().getName()+"存进:"+money);

}

}

public void subMoney(double money){

synchronized(this){

if(count < 0){

System.out.println("余额不足!");

return;

}

else{

count = count-money;

System.out.println(Thread.currentThread().getName()+"取出:"+money);

}

}

}

public void display(){

System.out.println("账户余额:"+count);

}

}

(5)不同步示例运行结果

\

(6)同步示例运行结果

\

13、线程同步带来的问题

同步线程如果出现问题或者遇到异常,会出现死锁问题。解决死锁的方案:不要同时锁定多个资源以及synchronized关键字的慎用。

(1)死锁示例:

此时线程A锁定了共享资源C,线程B锁定了共享资源D。但是线程A,B只有都获得过共享C和D,才能执行完操作E,接着去干其他的事儿。死锁的产生在于,线程A在等待线程B解锁共享资源D,线程B又在等待线程A解锁共享资源C。就这样处于一直等待状态。

\

(2)死锁示例:

举个更通俗易懂的例子就是,两个要吃饭,但是只有一双筷子可以使用。这两个人一人拿到了一根筷子,并且只能是用这一双筷子才能吃到饭。这两个都不想拿出自己的那一根筷子出来让别人先吃,所以就这样一直僵持下去,直到两个人都饿死。

(3)代码示例

public class ThreadTest {

public static void main(String[] args) {

MyRunnable m = new MyRunnable();

MyRunnable e = new MyRunnable();

m.flag=1;

e.flag=0;

Thread t1 = new Thread(m);

Thread t2 = new Thread(e);

t1.start();

t2.start();

}

}

/**

* 实现Runnable接口创建线程

*/

class MyRunnable implements Runnable{

public int flag;

static Object o1 = new Object(),o2 = new Object();

public void run(){

System.out.println(flag);

if(flag == 0){

synchronized(o1){

try {

System.out.println("11111");

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(o2){

System.out.println("xxoo");

}

}

}

if(flag == 1){

synchronized(o2){

try {

System.out.println("22222");

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(o1){

System.out.println("ooxx");

}

}

}

}

}

(4)示例运行结果

\

相关文章
最新文章
热点推荐