分享

Java线程及多线程技术及应用

 杰出天下 2011-11-23
  1、进程和线程的基础知识
  • 进程:运行中的应用程序称为进程,拥有系统资源(cpu、内存)

  • 线程:进程中的一段代码,一个进程中可以哦有多段代码。本身不拥有资源(共享所在进程的资源)

java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程。

区别: 1、是否占有资源问题

2、创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大。

3、进程为重量级组件,线程为轻量级组件


  • 多进程: 在操作系统中能同时运行多个任务(程序)

  • 多线程: 在同一应用程序中有多个功能流同时执行

2、线程的主要特点

  • 不能以一个文件名的方式独立存在在磁盘中;

  • 不能单独执行,只有在进程启动后才可启动;

  • 线程可以共享进程相同的内存(代码与数据)。

3、线程的主要用途

  • 利用它可以完成重复性的工作(如实现动画、声音等的播放)。

  • 从事一次性较费时的初始化工作(如网络连接、声音数据文件的加载)。

  • 并发执行的运行效果(一个进程多个线程)以实现更复杂的功能

4、多线程(多个线程同时运行)程序的主要优点

  • 可以减轻系统性能方面的瓶颈,因为可以并行操作;

  • 提高CPU的处理器的效率,在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性;另一方面,在多CPU系统中,可以把不同的线程在不同的CPU中执行,真正做到同时处理多任务。

6.2 线程创建与启动

1、与线程编程有关的一些概念

创建方式: 1 继承java.lang.Thread2 实现java.lang.Runnable接口

线程体:public void run()方法,其内的程序代码决定了线程的行为和功能。

线程启动: public void start () , 线程启动后,需要获取cpu才能自动调用run()运行。

线程休眠: public void sleep(long ms), 线程将暂停,放弃cpu


2、利用继承Thread类创建线程的示例

package com.px1987.j2se.thread.base;

/**通过Thread类实现多线程定义一个Thread的子类并重写其run方法.*/

public class MyThread extends Thread {

@Override

public void run() {

while (true) {

System.out.println("invoke MyThread run method");

}

}

public static void main(String[] args) { // main方法测试线程的创建与启动

MyThread myThread = new MyThread(); // 实例化MyThread的对象

myThread.start(); // 调用myThread对象的start方法启动一个线程

}

}

3、利用实现Runable接口创建线程的示例

package com.px1987.j2se.thread.base;

/**通过Runable接口实现多线程定义MyRunable类实现Runnable接口,并实现接口中的run方法。*/

public class MyRunable implements Runnable {

public void run() {

while (true)

System.out.println("invoke MyRunable run method");

}

public static void main(String[] args) { // main方法测试线程的创建与启动

// 建立MyRunable类的对象,以此对象为参数建立Thread类的对象

Thread thread = new Thread(new MyRunable());

thread.start(); // 调用thread对象的start方法启动一个线程

}

}

6.3 线程的状态控制

1、新建状态

new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。

2、就绪状态

处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。

3、死亡状态

死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个:

一个是正常运行的线程完成了它的全部工作

另一个是线程被强制性地终止,如通过执行stopdestroy方法来终止一个线程。

Method stop() & destroy() in the class Thread is deprecated

当一个线程进入死亡状态以后,就不能再回到其它状态了。让一个Thread对象重新执行一次的唯一方法,就是重新产生一个Thread对象。

4、体现线程状态转变的代码示例

package com.px1987.j2se.thread.base;

public class MyRunable1 implements Runnable {

public void run() {

while (true)

System.out.println("invoke MyRunable run method");

}

public static void main(String[] args) {

Thread thread = new Thread(new MyRunable()); // 新生状态

thread.start(); // 就绪状态,获得CPU后就能运行

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

thread.stop(); // 死亡状态

}

}

通过查API可以看到stop方法和destory方法已经过时了,所以不能再用,那要怎样做才能强制的销毁一个线程呢?


1、在run方法中执行return 线程同样结束

2、可以在while循环的条件中设定一个标志位,当它等于false的时候,while循环就不在运行,这样线程也就结束了。代码为实现的代码示例:


package com.px1987.j2se.thread.StateControl;

public class MyRunable2 implements Runnable {

private boolean isStop; //线程是否停止的标志位

public void run() {

while (!isStop)

System.out.println("invoke MyRunable run method");

}

public void stop(){ //终止线程

isStop=true;

}

public static void main(String[] args) {

MyRunable myRunable=new MyRunable();

Thread thread = new Thread(myRunable);

thread.start();

try {

Thread.sleep(5000);

}

catch (InterruptedException e) {

e.printStackTrace();

}

myRunable.stop(); //正确的停止线程的方法

}

}

5、阻塞状态

处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。

在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:

1sleep方法

可以调用Thread的静态方法:public static void sleep(long millis) throws InterruptedException 使得当前线程休眠(暂时停止执行millis毫秒)。由于是静态方法,sleep可以由类名直接调用:Thread.sleep(…)。下面为代码示例:

package com.px1987.j2se.thread.p5;

import java.util.Date;

import java.text.SimpleDateFormat;

class SleepTest implements Runnable {

private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

public void run() {

System.out.println("child thread begin");

int i = 0;

while (i++ < 5) {

System.out.println(format.format(new Date()));

try {

Thread.sleep(5000);

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("child thread dead at: " + format.format(new Date()));

}

public static void main(String[] args) {

Runnable r = new SleepTest();

Thread thread = new Thread(r);

thread.start();

try {

Thread.sleep(20000);

}

catch (InterruptedException e) {

e.printStackTrace();

}

thread.interrupt();

System.out.println("main method dead!");

}

}

该程序的运行结果如下:

child thread begin

2009-02-06 04:50:29

2009-02-06 04:50:34

2009-02-06 04:50:39

2009-02-06 04:50:44

main method dead!

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at com.px1987.j2se.thread.p5.Thread4.run(Thread4.java:17)

at java.lang.Thread.run(Unknown Source)

2009-02-06 04:50:49

child thread dead at: 2009-02-06 04:50:54

2yield方法

让出CPU的使用权,从运行态直接进入就绪态。下面为代码示例:

package com.px1987.j2se.thread.StateControl;

class Thread5 implements Runnable {

private String name;

Thread5(String s) {

this.name = s;

}

public void run() {

for (int i = 1; i <= 50; i++) {

System.out.println(name + ": " + i);

if (i % 10 == 0) {

Thread.yield();

}

}

}

}

package com.px1987.j2se.thread.StateControl;

public class YieldTest {

public static void main(String[] args) {

Runnable r1 = new Thread5("S1");

Runnable r2 = new Thread5("S2");

Thread t1 = new Thread(r1);

Thread t2 = new Thread(r2);

t1.start();

t2.start();

try {

Thread.sleep(2);

}

catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("main method over!");

}

}

该程序的部分运行结果如下:

S1: 20

S2: 7

S2: 8

S2: 9

S2: 10

S1: 41

S1: 42

S1: 43

S1: 44

S1: 45

S1: 46

S1: 47

S1: 48

S1: 49

S1: 50

S2: 11

S2: 12

3join方法

当某个(A)线程等待另一个线程(B)执行结束后,才继续执行时,使用join方法。Arun方法调用b.join()。下面为代码示例。

package com.px1987.j2se.thread.join;

class FatherThread implements Runnable {

public void run() {

System.out.println("爸爸想抽烟,发现烟抽完了");

System.out.println("爸爸让儿子去买包红塔山");

Thread son = new Thread(new SonThread());

son.start();

System.out.println("爸爸等儿子买烟回来");

try { //join含义:等待son线程执行完毕,father线程才继续执行

son.join();

}

catch (InterruptedException e) {

System.out.println("爸爸出门去找儿子跑哪去了");

System.exit(1);

}

System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");

}

}

package com.px1987.j2se.thread.join;

class SonThread implements Runnable {

public void run() {

String tabs="\t\t\t\t\t\t";

System.out.println(tabs+"儿子出门去买烟");

System.out.println(tabs+"儿子买烟需要10分钟");

try {

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

Thread.sleep(1000);

System.out.println(tabs+"儿子出去第" + ++i + "分钟");

}

}

catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(tabs+"儿子买烟回来了");

}

}

package com.px1987.j2se.thread.join;

public class JoinTest {

public static void main(String[] args) {

System.out.println("爸爸和儿子的故事");

Thread father = new Thread(new FatherThread());

father.start();

// try {

// Thread.sleep(5000);

// } catch (InterruptedException e) {

// e.printStackTrace();

// }

// father.interrupt();

}

}

该程序的运行结果如下:

爸爸和儿子的故事

爸爸想抽烟,发现烟抽完了

爸爸让儿子去买包红塔山

爸爸等儿子买烟回来

儿子出门去买烟

儿子买烟需要10分钟

儿子出去第1分钟

儿子出去第2分钟

儿子出去第3分钟

儿子出去第4分钟

儿子出去第5分钟

儿子出去第6分钟

儿子出去第7分钟

儿子出去第8分钟

儿子出去第9分钟

儿子出去第10分钟

儿子买烟回来了

爸爸高兴的接过烟开始抽,并把零钱给了儿

当时间来到儿子出去买烟的时候,Father线程调用interrupt方法就会打断son线程的正常执行,从而father线程也就不必等待son线程执行完毕再行动了,运行结果如下:

爸爸和儿子的故事

爸爸想抽烟,发现烟抽完了

爸爸让儿子去买包红塔山

爸爸等儿子买烟回来

儿子出门去买烟

儿子买烟需要10分钟

儿子出去第1分钟

儿子出去第2分钟

儿子出去第3分钟

儿子出去第4分钟

爸爸出门去找儿子跑哪去了

6.4线程的调度和优先级

1、线程的基本信息

方 法

功 能

isAlive()

判断线程是否还“活”着,即线程是否还未终止。

getPriority()

获得线程的优先级数值

setPriority()

设置线程的优先级数值

setName()

给线程一个名字

getName()

取得线程的名字

currentThread()

取得当前正在运行的线程对象,也就是取得自己本身

2、操作线程的基本信息代码示例

package com.px1987.j2se.thread.priority;

public class ThreadInfoTest {

public static void main(String[] argc) throws Exception {

Runnable r = new MyThread();

Thread t = new Thread(r, "Name test");

t.start();

System.out.println("name is: " + t.getName());

Thread.currentThread().sleep(5000);

System.out.println(t.isAlive());

System.out.println("over!");

}

}

class MyThread implements Runnable {

public void run() {

for (int i = 0; i < 100; i++)

System.out.println(i);

}

}

该程序的运行结果如下:

name is: Name test

0

1

2

3

. . .

97

98

99

false

over!


3线程的优先级

1)优先级(共10级):

它们决定线程执行的先后次序(优先级高者先执行)并可以通过Thread类中的setPriority()getPriority()方法来改变和获取优先级。典型的优先级码

    • Thread.MIN_PRIORITY 1级)

    • Thread.MAX_PRIORITY10级)

    • Thread.NORM_PRIORITY5级)

2)调度规则

Java是不支持线程时间片轮换的调度模型,而采用的是线程优先级高低的抢占调度模型。具有高优先级的线程可以抢占低优先级线程运行的机会。高优先级的线程将始终获得线程执行时间。但是这也不是绝对的,java线程调度器有可能会调用长期处于等待的线程进行执行,所以不要依靠线程的高优先级抢占模型去完成某些功能。

Java线程调度器支持不同优先级线程的抢先方式,但其本身不支持相同优先级线程的时间片轮换。但是如果java运行时系统所在的操作系统(如windows2000)支持时间片的轮换,则线程调度器就支持相同优先级线程的时间片轮换。

3)代码示例

package com.px1987.j2se.thread.priority;

public class ThreadPriorityTest {

public static void main(String[] args) {

Thread t1 = new Thread(new MyThread2(), "t1");

Thread t2 = new Thread(new MyThread2(), "t2");

t1.setPriority(1);

t2.setPriority(10);

t1.start();

t2.start();

}

}

class MyThread2 extends Thread {

public void run() {

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

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

yield();

}

}

}

该程序的运行结果如下:


t1: 0

t2: 0

t2: 1

t2: 2

t2: 3

t2: 4

t2: 5

t2: 6

t2: 7

t2: 8

t2: 9

t1: 1

t1: 2

t1: 3

t1: 4

t1: 5

t1: 6

t1: 7

t1: 8

t1: 9

6.5线程同步互斥

1线程同步互斥的一个示例

多个线程同时访问或操作同一资源时,很容易出现数据前后不一致的问题。请看下面的例子:


男孩拿着折子去北京银行海淀分行取钱

女孩拿着男孩的银行卡去西单百货疯狂购物

男孩走到柜台钱询问帐户余额

银行的业务员小姐亲切地告诉他:"您还有10000元!"

女孩看上了一件时髦的衣裳,准备买下

男孩在思考要取多少钱呢?

女孩到收银台准备刷卡消费

收银台刷卡机读取银行卡余额为10000

女孩买衣服刷卡消费5000

消费清单打印出来,消费:5000元 余额:5000

女孩离开商场

男孩思考了1毫秒

男孩决定取5000

银行的业务员小姐为男孩办理相关业务手续

交易完成

银行的业务员小姐告诉男孩:"您的余额为5000"

男孩离开银行


男孩帐户中一共有10000元,男孩拿着存折从银行取走5000元,女孩拿着男孩的银行卡购物刷卡消费5000元,最后男孩的帐户里却还剩5000元。显然这是不正确的,但是为什么会发生这样的情况呢?我们可以这样分析:男孩可以看作是一条线程,女孩也可以看作是一条线程,在同一时刻,两个线程都操作了同一个资源,那就是男孩的帐户。男孩从查看帐户余额到取走现金应该被看作是个原子性操作,是不可再分的,然而当男孩查看完余额正思考取多少钱的时候,女孩购物消费了5000元,也就是说女孩这条线程打断了男孩这条线程所要执行的任务。所以男孩刚查看完的余额10000元就不正确了,最终导致帐户中少减了5000元。

为了避免这样的事情发生,我们要保证线程同步互斥,所谓同步互斥就是:并发执行的多个线程在某一时间内只允许一个线程在执行以访问共享数据

2Java中线程互斥的实现机制

由多线程带来的性能改善是以可靠性为代价的,所以编程出线程安全的类代码是十分必要的。当多个线程可以访问共享资源(调用单个对象的属性和方法,对数据进行读、写、修改、删除等操作)时,应保证同时只有一个线程访问共享数据,Java对此提出了有效的解决方案—同步锁。任何线程要进入同步互斥方法(访问共享资源的方法或代码段)时,就必须得到这个共享资源对象的锁,线程进入同步互斥方法后其它线程则不能再进入同步互斥方法,直到拥有共享资源对象锁的线程执行完同步互斥方法释放了锁,下一个线程才能进入同步互斥方法被执行。

Java的这一线程互斥的实现机制可以用一个最通俗的比方来说明:比如公共卫生间就是一个共享资源,每个人都可以使用,但又不能同时使用,所以卫生间里有一把锁。一个人进去了,会把门锁上,其他人就不能进去。当Ta出来的时候,要打开锁,下一个人才能继续使用。

3、利用Synchronized关键字用于修饰同步互斥方法

1)同步互斥方法

public synchronized void method(){

//允许访问控制的代码

}

2)同步互斥代码块

synchronized(syncObject){

//允许访问控制的代码

}

3)锁定整个类

public synchronized class SyncObject{

}

由于synchronized 块可以针对任意的代码块,且可任意指定上锁的对象,因此灵活性较高。但要注意:

  • synchronized可以用来限定一个方法或一小段语句或整个类(该类中的所有方法都是synchronized方法)

  • 将访问共享数据的代码设计为synchronized方法

  • 由于可以通过 private 关键字来保证数据对象只能被方法访问,所以只需针对方法提出一套同步锁定机制。通过synchronized 方法来控制对类中的成员变量(共享数据)的访问。

  • 编写线程安全的代码会使系统的总体效率会降低,要适量使用

  • 只有某一个线程的synchronized方法执行完后其它线程的synchronized方法才能被执行。

  • 当前时间,只有一个线程访问被锁定的代码段,但不能保证其他线程去访问其他没有被锁定的代码段。因此所有对共享资源进行操作的代码段都应该加锁。

  • 对数据库操作时,修改数据的线程要加锁,而读数据的线程可以不加锁

有了这种解决方案,我们用线程安全的代码来重新实现一下男孩和女孩取钱的故事。以下是核心代码:

package com.px1987.j2se.thread.synchronous.v2;

/** 帐户类 */

public class Account {

/** 余额 */

private int balance;

public Account(int balance) {

this.balance = balance;

}

}

package com.px1987.j2se.thread.synchronous.v2;

/** 男孩类,实现Runnable接口*/

public class Boy implements Runnable {

/** 银行帐户*/

Account account;

public Boy(Account account) {

this.account = account;

}

/** 男孩拿着折子去北京银行海淀分行取钱*/

public void run() {

System.out.println("男孩拿着折子去北京银行海淀分行取钱");

synchronized (account) {

System.out.println("男孩走到柜台钱询问帐户余额");

int balance = account.getBalance();

System.out.println("银行的业务员小姐亲切地告诉他:\"您还有" +

balance + "元!\"");

try {

System.out.println("男孩在思考要取多少钱呢?");

Thread.sleep(1);

System.out.println("男孩思考了1毫秒");

}

catch (InterruptedException e) {

e.printStackTrace();

}

int money = 5000;

System.out.println("男孩决定取" + money + "");

System.out.println("银行的业务员小姐为男孩办理相关业务手续");

account.setBalance(balance - money);

System.out.println("交易完成");

System.out.println("银行的业务员小姐告诉男孩:\"您的余额为" +

account.getBalance()+ "\"");

}

System.out.println("男孩离开银行");

}

}

package com.px1987.j2se.thread.synchronous.v2;

/** 女孩类,实现runnable接口*/

public class Girl implements Runnable {

/** 女孩持有男孩的银行卡*/

Account account;

public Girl(Account account) {

this.account = account;

}

/*** "女孩拿着小军的银行卡去西单百货疯狂购物*/

public void run() {

String tabs = "\t\t\t\t\t\t";

System.out.println(tabs + "女孩拿着小军的银行卡去西单百货疯狂购物");

System.out.println(tabs + "女孩看上了一件时髦的衣裳准备买下");

synchronized (account) {

System.out.println(tabs + "女孩到收银台准备刷卡消费");

int balance = account.getBalance();

System.out.println(tabs + "收银台刷卡机读取银行卡余额为" + balance + "");

int payout = 5000;

System.out.println(tabs + "女孩买衣服刷卡消费" + payout + "");

account.setBalance(balance - payout);

System.out.println(tabs + "消费清单打印出来消费" + payout + "" + " 余额"

+ account.getBalance() + "");

}

System.out.println(tabs + "女孩离开商场");

}

}

package com.px1987.j2se.thread.synchronous.v2;

public class Bank {

public static void main(String[] args) {

Account account=new Account(10000);

Thread boyThread=new Thread(new Boy(account));

Thread girlThread=new Thread(new Girl(account));

boyThread.start();

girlThread.start();

}

}

修改后的代码运行结果如下图:

男孩拿着折子去北京银行海淀分行取钱

女孩拿着小军的银行卡去西单百货疯狂购物

女孩看上了一件时髦的衣裳,准备买下

女孩到收银台准备刷卡消费

收银台刷卡机读取银行卡余额为10000

女孩买衣服刷卡消费5000

消费清单打印出来,消费:5000元 余额:5000

女孩离开商场

男孩走到柜台钱询问帐户余额

银行的业务员小姐亲切地告诉他:"您还有5000元!"

男孩在思考要取多少钱呢?

男孩思考了1毫秒

男孩决定取5000

银行的业务员小姐为男孩办理相关业务手续

交易完成

银行的业务员小姐告诉男孩:"您的余额为0"

男孩离开银行


从结果中可以看出来,男孩从查看余额到取钱,女孩没有操作帐户,所以最后的余额是正确的。

4、线程死锁

使用互斥锁容易产生死锁问题。比如:一个线程需要锁定两个对象才能完成,线程1拥有对象A的锁,线程1如果再拥有对象B的锁就能完成操作,线程2拥有对象B的锁,线程2如果再拥有对象A的锁就能完成操作。

很不幸的是线程1执行不下去了,因为线程1等待的资源对象B被线程2锁住了,线程2也执行不下去了,因为线程2等待的资源对象A被线程1锁住了,这样就造成了死锁。


阅读一段文字:由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。

导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。


1)死锁问题的一个代码示例

package com.px1987.j2se.thread.DeadLock;

class Thread1 implements Runnable {

private Object a;

private Object b;

public Thread1(Object a, Object b) {

super();

this.a = a;

this.b = b;

}

public void run() {

synchronized (a) {

System.out.println("Thread1获得对象a的锁");

try {

Thread.sleep(1);

}

catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (b) {

System.out.println("Thread1获得对象b的锁");

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

package com.px1987.j2se.thread.DeadLock;

class Thread2 implements Runnable {

private Object a;

private Object b;

public Thread2(Object a, Object b) {

super();

this.a = a;

this.b = b;

}

public void run() {

synchronized (b) {

System.out.println("Thread2获得对象b的锁");

try {

Thread.sleep(1);

}

catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (a) {

System.out.println("Thread2获得对象a的锁");

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

package com.px1987.j2se.thread.DeadLock;

public class TestDeadLock {

public static void main(String[] args) {

Object a=new Object();

Object b=new Object();

Thread thread1=new Thread(new Thread1(a,b));

Thread thread2=new Thread(new Thread2(a,b));

thread1.start();

thread2.start();

}

}

下面是运行结果:

2)死锁问题的另一个代码示例

package com.px1987.j2se.thread.DeadLock;

public class ThreadDeadLock {

public static void main(String[] args) {

ThreadOne threadOne=new ThreadOne();

ThreadTwo threadTwo=new ThreadTwo();

String s1="s";

String s2="sss";

threadOne.op1=s1;

threadTwo.op1=s1;

threadOne.op2=s2;

threadTwo.op2=s2;

threadOne.start();

threadTwo.start();

}

}

class ThreadOne extends Thread{

String op1;

String op2;

public void run(){// 同步中又有同步,就可能死锁

synchronized(op1){

System.out.println(Thread.currentThread().getName()+"锁定op1");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(op2){

System.out.println(Thread.currentThread().getName()+"锁定op2");

}

}

}

}

class ThreadTwo extends Thread{

String op1;

String op2;

public void run(){

synchronized(op2){

System.out.println(Thread.currentThread().getName()+"锁定op2");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(op1){

System.out.println(Thread.currentThread().getName()+"锁定op1");

}

}

}

}

6.6生产者消费者问题

1、生产者消费者问题的示例

生产者消费者问题也是一个典型的线程问题。我们举一个这方面的实例来说明:在一个果园里,有农夫和小孩,农夫会不停的采摘水果放入果园中心的一个水果筐直到水果筐满,而小孩会不停的从水果筐里拿水果来吃,直到水果拿完。分析这个模型我们可以看出:农夫可以看成是一个生产者的线程,小孩可以看成是一个消费者的线程,而大水果筐是共享资源。

2、用Java程序表述的代码示例

package com.px1987.j2se.thread.ProducerConsumer;

import java.util.Random;

/*** 水果类*/

public class Fruit {

/*** 水果编号*/

private int id;

/*** 水果编号计数器*/

private static int number = 0;

/*** 水果品种 */

private String variety;

/*** 水果品种数组 */

private String[] varietys = "苹果,桃子,梨子,香蕉,西瓜,荔枝,葡萄".split(",");

public Fruit() {

super();

this.variety = varietys[new Random().nextInt(7)];

this.id = ++number;

}

}


水果筐应该设计成类似于栈的数据结构,其中包含一个数组来存放筐里的水果,而数组的下标就是水果筐的容量。设定一个索引index表示指向下一个将要放入水果的位置。类中的push方法模拟农夫向水果筐中放入水果,pop方法模拟小孩从水果筐中拿水果。这两个方法都要操作共享资源,所以pushpop方法都是同步互斥方法。

3、如何避免出现死锁

那同步的问题解决后是否会出现死锁呢?大家试想一下,如果生产的速度大于消费的速度就会导致功大于求,水果筐很容易就满了,然而生产者又一直抱着水果筐不放,没有机会给消费者使用,消费者不消费生产者就无法生产,所以就造成了死锁。

怎样解决呢?在两个同步互斥方法中用到了waitnotify方法,这两个方法是为了防止死锁的。

  • waitObject类的方法,它的作用是拥有互斥锁的线程放弃锁的使用权,进入wait池进行等待,那么互斥锁就有可能被其他线程获得以执行其他任务。

  • notify也是Object类的方法,它的作用是从wait池中唤醒一条正在等待的线程进入就绪状态,被唤醒的这条线程就很可能重新获得cup和互斥锁来完成它的任务。

  • notifyAllNotify很相似,它是从wait池中唤醒所有正在等待的线程进入就绪状态。

需要注意的是以上三个方法都只能在synchronized方法中应用,否者会出现下面的异常信息:IllegalMonitorStateExceptioncurrent thread not owner

4、实现的代码示例

package com.px1987.j2se.thread.ProducerConsumer;

import java.text.DecimalFormat;

import java.util.Arrays;

/*** 水果框类,类似一个栈的模型 */

public class FruitBasket {

/*** 容量为10的水果数组,也就是说水果框最多能放下10个水果 */

private Fruit[] fruits = new Fruit[10];

/*** 下一个将要放入水果的位置*/

private int index = 0;

/*** 水果框中是否为空 @return true为空,false为不空 */

public boolean isEmpty() {

return index == 0 ? true : false;

}

/*** 水果框是否装满* @return true为满,false为未满*/

public boolean isFull() {

return index == fruits.length ? true : false;

}

/*** 进栈方法,模拟农夫把水果放入筐中,@param name 农夫的名字,@param fruit 水果对象 */

public synchronized void push(String name, Fruit fruit) {

//while循环,不用if,避免IndexOutOfBoundsException异常的产生

while (isFull()) {

//如果水果筐满了,需要等待

try {

this.wait();

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

//将水果放入index指示的位置,index再上移一格

fruits[index++] = fruit;

System.out.println(name + " 向水果框中放入编号为" + fruit.getId() + ""+

fruit.getVariety());

display();

this.notify(); //通知其他等待的农夫或孩子可以开始工作啦

}

/*** 出栈方法,模拟小孩从水果筐中取出水果,@param name 小孩的名字,@return 取出的水果*/

public synchronized Fruit pop(String name) {

//while循环,不用if,避免IndexOutOfBoundsException异常的产生

while (isEmpty()) {

try { //如果水果筐空,需要等待

this.wait();

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

Fruit fruit = null;

fruit = fruits[--index]; //index下移一位,取出指示位置上的水果

System.out.println(name + " 从水果框中拿出编号为" + fruit.getId() + ""+

fruit.getVariety());

display();

this.notify();

return fruit;

}

/*** 显示水果筐中水果存放情况*/

public void display() {

for (int i = 0; i < index; i++)

System.out.printf("%-10s", " NO:" +

new DecimalFormat("00").format(fruits[i].getId())

+ fruits[i].getVariety() + " |");

for (int i = index; i < fruits.length; i++) {

System.out.printf("%-10s", " " + (i + 1) + "|");

}

System.out.println();

}

}

package com.px1987.j2se.thread.ProducerConsumer;

import java.util.Random;

/** 果园里的农夫类,他是生产者,实现Runnable接口*/

public class Farmer implements Runnable {

/** 姓名*/

private String name;

/** 水果框*/

private FruitBasket fruitBasket;

/** 农夫会不停地重复这一系列动作:从水果树上采摘一个水果放入水果框中,然后随机的休息0-2*/

public void run() {

while (true) {

fruitBasket.push(name, new Fruit());

try {

Thread.sleep(new Random().nextInt(2000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public Farmer(String name, FruitBasket fruitBasket) {

super();

this.name = name;

this.fruitBasket = fruitBasket;

}

}

package com.px1987.j2se.thread.ProducerConsumer;

import java.util.Random;

/*** 果园中的小孩类,他是消费者,实现Runnable接口*/

public class Child implements Runnable {

/*** 姓名*/

private String name;

/*** 水果框*/

private FruitBasket fruitBasket;

/*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/

public void run() {

while (true) {

fruitBasket.pop(name);

try {

Thread.sleep(new Random().nextInt(5000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public Child(String name, FruitBasket fruitBasket) {

super();

this.name = name;

this.fruitBasket = fruitBasket;

}

}

package com.px1987.j2se.thread.ProducerConsumer;

import java.util.Random;

/*** 果园中的小孩类,他是消费者,实现Runnable接口*/

public class Child implements Runnable {

/*** 姓名*/

private String name;

/*** 水果框*/

private FruitBasket fruitBasket;

/*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/

public void run() {

while (true) {

fruitBasket.pop(name);

try {

Thread.sleep(new Random().nextInt(5));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public Child(String name, FruitBasket fruitBasket) {

super();

this.name = name;

this.fruitBasket = fruitBasket;

}

}

测试时使用多个生产者线程和多个消费者线程

package com.px1987.j2se.thread.ProducerConsumer;

/** 果园类,测试*/

public class Orchard {

public static void main(String[] args) {

FruitBasket fruitBasket = new FruitBasket();

Thread farmerThread1 = new Thread(new Farmer("农夫1", fruitBasket));

Thread farmerThread2 = new Thread(new Farmer("农夫2", fruitBasket));

Thread farmerThread3 = new Thread(new Farmer("农夫3", fruitBasket));

Thread childThread1 = new Thread(new Child("小孩1", fruitBasket));

Thread childThread2 = new Thread(new Child("小孩2", fruitBasket));

Thread childThread3 = new Thread(new Child("小孩3", fruitBasket));

farmerThread1.start();

farmerThread2.start();

farmerThread3.start();

childThread1.start();

childThread2.start();

childThread3.start();

}

}

程序的运行结果如下:


农夫1 向水果框中放入编号为1的苹果

NO:01苹果 | 2| 3| 4| 5| 6|

农夫3 向水果框中放入编号为2的荔枝

NO:01苹果 | NO:02荔枝 | 3| 4| 5| 6|

小孩2 从水果框中拿出编号为2的荔枝

NO:01苹果 | 2| 3| 4| 5| 6|

农夫2 向水果框中放入编号为3的香蕉

NO:01苹果 | NO:03香蕉 | 3| 4| 5| 6|

小孩1 从水果框中拿出编号为3的香蕉

NO:01苹果 | 2| 3| 4| 5| 6|

小孩3 从水果框中拿出编号为1的苹果

1| 2| 3| 4| 5| 6|

农夫2 向水果框中放入编号为4的苹果

NO:04苹果 | 2| 3| 4| 5| 6|

小孩1 从水果框中拿出编号为4的苹果

1| 2| 3| 4| 5| 6|

农夫1 向水果框中放入编号为5的苹果

NO:05苹果 | 2| 3| 4| 5| 6|

农夫3 向水果框中放入编号为6的西瓜

NO:05苹果 | NO:06西瓜 | 3| 4| 5| 6|

农夫2 向水果框中放入编号为7的苹果

NO:05苹果 | NO:06西瓜 | NO:07苹果 | 4| 5| 6|

小孩3 从水果框中拿出编号为7的苹果

NO:05苹果 | NO:06西瓜 | 3| 4| 5| 6|

小孩3 从水果框中拿出编号为6的西瓜

NO:05苹果 | 2| 3| 4| 5| 6|

小孩2 从水果框中拿出编号为5的苹果

1| 2| 3| 4| 5| 6|

农夫2 向水果框中放入编号为8的桃子

NO:08桃子 | 2| 3| 4| 5| 6|

农夫1 向水果框中放入编号为9的荔枝

NO:08桃子 | NO:09荔枝 | 3| 4| 5| 6|

农夫3 向水果框中放入编号为10的香蕉

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | 4| 5| 6|

农夫1 向水果框中放入编号为11的桃子

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | 5| 6|

农夫1 向水果框中放入编号为12的荔枝

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 6|

农夫3 向水果框中放入编号为13的西瓜

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:13西瓜 |

小孩1 从水果框中拿出编号为13的西瓜

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 6|

农夫2 向水果框中放入编号为14的西瓜

NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:14西瓜 |

6.7 课后复习题

1、线程的应用示例——继承Thread

import java.util.*;

public class ThreadExampleOne{

public static void main(String args[]){

TimeThread timeThread = new TimeThread();

timeThread.start();

}

}

class TimeThread extends Thread{

Date nowTime;

public void run(){ //重写run方法

while(true){

nowTime=new Date();

System.out.println("现在的时间为:"+nowTime.getHours()+

":"+nowTime.getMinutes()+":"+nowTime.getSeconds());

try{

sleep(1000);

}

catch(InterruptedException e){

System.out.println(e.toString());

}

}

}

}

2、线程的应用示例——实现Runnable接口

import java.util.*;

public class ThreadExampleTwo{

public static void main(String args[]){

TimeThread timeThreadObj = new TimeThread();

}

}

class TimeThread extends Object implements Runnable{

Date nowTime;

Thread timeThread=null;

public TimeThread(){

timeThread=new Thread(this);

timeThread.start();

}

public void run(){ //重写run方法

while(true){

nowTime=new Date();

System.out.println("现在的时间为:"+nowTime.getHours()+

":"+nowTime.getMinutes()+":"+nowTime.getSeconds());

try{

timeThread.sleep(1000);

}

catch(InterruptedException e){

System.out.println(e.toString());

}

}

}

}

3线程优先级的应用示例

class ThreadPriorityDemo extends Thread{

ThreadPriorityDemo(String strName){

System.out.println("线程名称"+strName);

}

public void run(){

//打印出当前线程的优先级

System.out.println(this.getPriority());

}

}

class PriorityDemo{

public static void main(String args[]){

ThreadPriorityDemo aThreadDemo = new ThreadPriorityDemo("Thread A");

aThreadDemo.setPriority(Thread.MAX_PRIORITY);

aThreadDemo.start();

ThreadPriorityDemo bThreadDemo = new ThreadPriorityDemo("Thread B");

bThreadDemo.setPriority(Thread.MIN_PRIORITY);

bThreadDemo.start();

ThreadPriorityDemo cThreadDemo = new ThreadPriorityDemo("Thread C");

cThreadDemo.setPriority(Thread.NORM_PRIORITY);

cThreadDemo.start();

ThreadPriorityDemo dThreadDemo = new ThreadPriorityDemo("Thread D");

dThreadDemo.setPriority(Thread.MIN_PRIORITY);

dThreadDemo.start();

ThreadPriorityDemo eThreadDemo = new ThreadPriorityDemo("Thread E");

eThreadDemo.setPriority(Thread.MIN_PRIORITY);

eThreadDemo.start();

}

}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多