多线程实验心得

2024-06-11

多线程实验心得(共5篇)

多线程实验心得 篇1

宁波工程学院电信学院计算机教研室

实验报告

课程名称: Java 2 姓 名: *** 实验项目: 多线程实验 学 号: **** 指导教师: **** 班 级: **** 实验位置: 电信楼机房 日 期:

一、实验目的

1、掌握多线程编程的特点和工作原理;

2、掌握编写线程程序的方法

3、了解线程的调度和执行过程

4、掌握线程同步机理

二、实验环境

windows记事本,java jdk 1.60版本,cmd命令运行窗口

三、实验内容 实验一:

应用Java中线程的概念写一个Java程序(包括一个测试线程程序类TestThread,一个Thread类的子类PrintThread)。在测试程序中用子类PrintThread创建2个线程,使得其中一个线程运行时打印10次“线程1正在运行”,另一个线程运行时打印5次“线程2正在运行

源程序:

public class A { public static void main(String args[]){

Test1 A1;

Test2 A2;

A1=new Test1();

A2=new Test2();

A1.start();

A2.start();} } class PrintThread extends Thread { } class Test1 extends PrintThread { public void run(){

for(int i=1;i<=10;i++)

{

System.out.println(“线程1正在运行!”);

} } } class Test2 extends PrintThread { public void run(){

for(int i=1;i<=5;i++)

{

System.out.println(“线程2正在运行!”);

} } } 运行结果:

实验二:

将上述程序用Runnable接口改写,并上机验证源程序 public class D { public static void main(String args[]){

Move move=new Move();

move.test1.start();

move.test2.start();} } class Move implements Runnable { Thread test1,test2;Move(){

test1=new Thread(this);

test1.setName(“线程1正在运行!”);

test2=new Thread(this);

test2.setName(“线程2正在运行!”);} public void run(){

if(Thread.currentThread()==test1)

{

for(int i=1;i<=10;i++)

{

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

} } } else { for(int i=1;i<=5;i++){

System.out.println(test2.getName());} } 运行结果:

实验三:

import java.awt.*;import java.awt.event.*;public class E

{ public static void main(String args[])

{ new FrameMoney();

} } class FrameMoney extends Frame implements Runnable,ActionListener { int money=100;

TextArea text1,text2;

Thread 会计,出纳;

int weekDay;

Button start=new Button(“开始演示”);

FrameMoney()

{ 会计=new Thread(this);

出纳=new Thread(this);

text1=new TextArea(12,15);

text2=new TextArea(12,15);

setLayout(new FlowLayout());

add(start);

add(text1);

add(text2);

setVisible(true);

setSize(360,300);

validate();

addWindowListener(new WindowAdapter()

{ public void windowClosing(WindowEvent e)

{System.exit(0);

}

});

start.addActionListener(this);

}

public void actionPerformed(ActionEvent e)

{ if(!(出纳.isAlive()))

{ 会计=new Thread(this);

出纳=new Thread(this);

}

try

{ 会计.start();

出纳.start();

}

catch(Exception exp){}

}

public synchronized void 存取(int number)//存取方法

{ if(Thread.currentThread()==会计)

{ text1.append(“今天是星期”+weekDay+“n”);

for(int i=1;i<=3;i++)//会计使用存取方法存入90元,存入30元,稍歇一下

{ money=money+number;

//这时出纳仍不能使用存取方法

try { Thread.sleep(1000);//因为会计还没使用完存取方法

}

catch(InterruptedException e){}

text1.append(“帐上有”+money+“万n”);

}

}

else if(Thread.currentThread()==出纳)

{ text2.append(“今天是星期 ”+weekDay+“n”);

for(int i=1;i<=2;i++)//出纳使用存取方法取出30元,取出15元,稍歇一下

{ money=money-number/2;

//这时会计仍不能使用存取方法

try { Thread.sleep(1000);//因为出纳还没使用完存取方法

}

catch(InterruptedException e){}

text2.append(“帐上有”+money+“万n”);

}

}

}

public void run()

{ if(Thread.currentThread()==会计||Thread.currentThread()==出纳)

{ for(int i=1;i<=3;i++)//从周一到周三会计和出纳都要使用帐本

{ weekDay=i;

存取(30);

}

}

} }

运行结果:

}

四、实验心得与小结

通过本次实验,基本了解了线程的概念,作用,方法以及使用规则。1.首先:java 程序是建立在线程之上的。.2.创建线程必须继承 Thread class 它已经为线程的创建和运行做了必要的配置。run是线程就重要的方法。你必须覆写这个方法达到你想要的目的。3.run方法所包含的代码就是和其他线程同时运行的代码以达到同一时刻运行多段代码的目的。当终止了 run以后。这个线程也就结束了。调用线程的 start方法才会执行 run方法。

4.线程的生命周期:新建——Thread.State.NEW:当一个 Thread 类或者其子类的对象被声明并创建时,新的线程对象处于新建状态,此时它已经有了相应的内存空间和其他资源start方法尚未被调整用就绪可执行状态——Thread.State.RUNNABLE:处于新建状态的线程被启动后,将进入线程队列排队,这个时候具备了运行的条件,一旦轮到 CPU 的时候,就可以脱离创建它的主线程独立开始自己的生命周期运行:就绪的线程被调度进入运行状态,每一个 Thread 类及其子类的对象都有一个重要的run方法,当线程对象被调度执行的时候,它将自动调用本对象的 run方法,从第一句代码开始执行。

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

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

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方法模拟小孩从水果筐中拿水果。这两个方法都要操作共享资源,所以push和pop方法都是同步互斥方法。

3、如何避免出现死锁

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

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

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

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

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

需要注意的是以上三个方法都只能在synchronized方法中应用,否者会出现下面的异常信息:IllegalMonitorStateException:current 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());

} 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的西瓜

Java多线程编程总结 篇3

2007-05-17 11:21:59 标签:java 多线程

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处、作者信息和本声明。否则将追究法律责任。http://lavasoft.blog.51cto.com/62575/27069

Java多线程编程总结

下面是Java线程系列博文的一个编目:

Java线程:概念与原理 Java线程:创建与启动

Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:线程的调度-优先级 Java线程:线程的调度-让步 Java线程:线程的调度-合并 Java线程:线程的调度-守护线程 Java线程:线程的同步-同步方法 Java线程:线程的同步-同步块

Java线程:并发协作-生产者消费者模型 Java线程:并发协作-死锁 Java线程:volatile关键字 Java线程:新特征-线程池

Java线程:新特征-有返回值的线程 Java线程:新特征-锁(上)Java线程:新特征-锁(下)Java线程:新特征-信号量 Java线程:新特征-阻塞队列 Java线程:新特征-阻塞栈 Java线程:新特征-条件变量 Java线程:新特征-原子量 Java线程:新特征-障碍器 Java线程:大总结

----

下面的内容是很早之前写的,内容不够充实,而且是基于Java1.4的内容,Java5之后,线程并发部分扩展了相当多的内容,因此建议大家看上面的系列文章的内容,与时俱进,跟上Java发展的步伐。----

一、认识多任务、多进程、单线程、多线程 要认识多线程就要从操作系统的原理说起。

以前古老的DOS操作系统(V 6.22)是单任务的,还没有线程的概念,系统在每次只能做一件事情。比如你在copy东西的时候不能rename文件名。为了提高系统的利用效率,采用批处理来批量执行任务。

现在的操作系统都是多任务操作系统,每个运行的任务就是操作系统所做的一件事情,比如你在听歌的同时还在用MSN和好友聊天。听歌和聊天就是两个任务,这个两个任务是“同时”进行的。一个任务一般对应一个进程,也可能包含好几个进程。比如运行的MSN就对应一个MSN的进程,如果你用的是windows系统,你就可以在任务管理器中看到操作系统正在运行的进程信息。

一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。

多线程的目的是为了最大限度的利用CPU资源。

Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。

一般常见的Java应用程序都是单线程的。比如,用java命令运行一个最简单的HelloWorld的Java应用程序时,就启动了一个JVM进程,JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出。

对于一个进程中的多个线程来说,多个线程共享进程的内存块,当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,速度也很快。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。

实际上,操作的系统的多进程实现了多任务并发执行,程序的多线程实现了进程的并发执行。多任务、多进程、多线程的前提都是要求操作系统提供多任务、多进程、多线程的支持。

在Java程序中,JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。

所谓的“并发执行”、“同时”其实都不是真正意义上的“同时”。众所周知,CPU都有个时钟频率,表示每秒中能执行cpu指令的次数。在每个时钟周期内,CPU实际上只能去执行一条(也有可能多条)指令。操作系统将进程线程进行管理,轮流(没有固定的顺序)分配每个进程很短的一段是时间(不一定是均分),然后在每个线程内部,程序代码自己处理该进程内部线程的时间分配,多个线程之间相互的切换去执行,这个切换时间也是非常短的。因此多任务、多进程、多线程都是操作系统给人的一种宏观感受,从微观角度看,程序的运行是异步执行的。

用一句话做总结:虽然操作系统是多线程的,但CPU每一时刻只能做一件事,和人的大脑是一样的,呵呵。

二、Java与多线程

Java语言的多线程需要操作系统的支持。

Java 虚拟机允许应用程序并发地运行多个执行线程。Java语言提供了多线程编程的扩展点,并给出了功能强大的线程控制API。

在Java中,多线程的实现有两种方式: 扩展java.lang.Thread类 实现java.lang.Runnable接口

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。

非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

三、扩展java.lang.Thread类

/** * File Name: TestMitiThread.java * Created by: IntelliJ IDEA.* Copyright: Copyright(c)2003-2006 * Company: Lavasoft([url]http://lavasoft.blog.51cto.com/[/url])* Author: leizhimin * Modifier: leizhimin * Date Time: 2007-5-17 10:03:12 * Readme: 通过扩展Thread类实现多线程 */ public class TestMitiThread { public static void main(String[] rags){ System.out.println(Thread.currentThread().getName()+ “ 线程运行开始!”);new MitiSay(“A”).start();new MitiSay(“B”).start();System.out.println(Thread.currentThread().getName()+ “ 线程运行结束!”);} }

class MitiSay extends Thread { public MitiSay(String threadName){ super(threadName);}

public void run(){ System.out.println(getName()+ “ 线程运行开始!”);for(int i = 0;i < 10;i++){ System.out.println(i + “ ” + getName());try { sleep((int)Math.random()* 10);} catch(InterruptedException e){ e.printStackTrace();} } System.out.println(getName()+ “ 线程运行结束!”);} }

运行结果:

main 线程运行开始!main 线程运行结束!A 线程运行开始!0 A 1 A B 线程运行开始!2 A 0 B 3 A 4 A 1 B 5 A 6 A 7 A 8 A 9 A A 线程运行结束!2 B 3 B 4 B 5 B 6 B 7 B 8 B 9 B B 线程运行结束!说明:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

在一个方法中调用Thread.currentThread().getName()方法,可以获取当前线程的名字。在mian方法中调用该方法,获取的是主线程的名字。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。

Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

四、实现java.lang.Runnable接口

/** * 通过实现 Runnable 接口实现多线程 */ public class TestMitiThread1 implements Runnable {

public static void main(String[] args){ System.out.println(Thread.currentThread().getName()+ “ 线程运行开始!”);TestMitiThread1 test = new TestMitiThread1();Thread thread1 = new Thread(test);Thread thread2 = new Thread(test);thread1.start();thread2.start();System.out.println(Thread.currentThread().getName()+ “ 线程运行结束!”);}

public void run(){ System.out.println(Thread.currentThread().getName()+ “ 线程运行开始!”);for(int i = 0;i < 10;i++){ System.out.println(i + “ ” + Thread.currentThread().getName());try { Thread.sleep((int)Math.random()* 10);} catch(InterruptedException e){ e.printStackTrace();} } System.out.println(Thread.currentThread().getName()+ “ 线程运行结束!”);} }

运行结果:

main 线程运行开始!Thread-0 线程运行开始!main 线程运行结束!0 Thread-0 Thread-1 线程运行开始!0 Thread-1 1 Thread-1 1 Thread-0 2 Thread-0 2 Thread-1 3 Thread-0 3 Thread-1 4 Thread-0 4 Thread-1 5 Thread-0 6 Thread-0 5 Thread-1 7 Thread-0 8 Thread-0 6 Thread-1 9 Thread-0 7 Thread-1 Thread-0 线程运行结束!8 Thread-1 9 Thread-1 Thread-1 线程运行结束!说明:

TestMitiThread1类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

五、读解Thread类API

static int MAX_PRIORITY 线程可以具有的最高优先级。static int MIN_PRIORITY 线程可以具有的最低优先级。static int NORM_PRIORITY 分配给线程的默认优先级。

构造方法摘要

Thread(Runnable target)分配新的 Thread 对象。Thread(String name)分配新的 Thread 对象。

方法摘要

static Thread currentThread()返回对当前正在执行的线程对象的引用。ClassLoader getContextClassLoader()返回该线程的上下文 ClassLoader。long getId()返回该线程的标识符。String getName()返回该线程的名称。int getPriority()返回线程的优先级。Thread.State getState()返回该线程的状态。ThreadGroup getThreadGroup()返回该线程所属的线程组。static boolean holdsLock(Object obj)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。void interrupt()中断线程。

static boolean interrupted()测试当前线程是否已经中断。boolean isAlive()测试线程是否处于活动状态。boolean isDaemon()测试该线程是否为守护线程。boolean isInterrupted()测试线程是否已经中断。void join()等待该线程终止。void join(long millis)等待该线程终止的时间最长为 millis 毫秒。void join(long millis, int nanos)等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。void resume()已过时。该方法只与 suspend()一起使用,但 suspend()已经遭到反对,因为它具有死锁倾向。有关更多信息,请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。void setContextClassLoader(ClassLoader cl)设置该线程的上下文 ClassLoader。void setDaemon(boolean on)将该线程标记为守护线程或用户线程。

static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。void setName(String name)改变线程名称,使之与参数 name 相同。void setPriority(int newPriority)更改线程的优先级。

void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)设置该线程由于未捕获到异常而突然终止时调用的处理程序。static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。static void sleep(long millis, int nanos)在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。void stop()已过时。该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。有关更多信息,请参阅《为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。void stop(Throwable obj)已过时。该方法具有固有的不安全性。请参阅 stop()以获得详细信息。该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。有关更多信息,请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。void suspend()已过时。该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。有关更多信息,请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。String toString()返回该线程的字符串表示形式,包括线程名称、优先级和线程组。static void yield()暂停当前正在执行的线程对象,并执行其他线程。

六、线程的状态转换图

线程在一定条件下,状态会发生变化。线程变化的状态转换图如下:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

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)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(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中已经废除,不再介绍。因为有死锁倾向。

7、常见线程名词解释

主线程:JVM调用程序mian()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

ios 开发中的多线程 篇4

1,iOS中只有主线程有直接修改Ui的权利

2,iPhone中主线程堆栈1M,新开辟的线程堆栈512K

3,多任务,多核,效率,用户体验共同决定

(一)GCD(Grand Central dispatch)block和dispatch,可以简化多核多线程编程,iOS4以后支持

1,block的定义类似于函数指针;block对象(块对象);

可以存在block数组;

但block存储在函数栈中,注意大括号中的生命周期;

2,block典型用法,图片下载应用中的块应用,嵌套异步block块的使用,如果前面ok,则去UI更新

dispatch_async(dispatch_queue_create(“com.enormego.EGOImageLoader”,NULL), ^{

UIImage* image = styler(anImage);

[[EGOCache currentCache] setImage:image forKey:keyForURL(aURL, style) withTimeoutInterval:604800];

dispatch_async(dispatch_get_main_queue, ^{

completion(image, aURL, nil);

});

});

(二)NSOperation和NSOpertionQueue

1,一个继承自NSOperation的操作类,该类的实现中必须有 (void) main()方法

2,最简单的方法,将NSOperation的实例放入NSOpertionQueue中

3,可以在NSOpertionQueue中设置同时可以进行的操作数

(三)NSThread

1,detachNewThreadSelector此为简便方法,不用进行线程清理

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

2,NSThread initialWithTarget, (void)start;方法,可以创建线程,但选择合适的时机启动线程

NSThread* myThread = [[NSThread alloc] initWithTarget:self

selector:@selector(myThreadMainMethod:)

object:nil];

[myThread start];

(四)线程间同步

1,原子锁属性,automic,noautomic,变量可以保证多线程访问

2,NSCondition,可以提供带条件的同步锁,解锁时,需要输入相同的条件才能解锁

3,NSLock, lock,unlock比较简单的同步锁

多线程实验心得 篇5

问题:进程和线程的区别

解答:一个进程对应一个程序的执行,而一个线程则是进程执行过程中的一个单独的执行序列,一个进程可以包含多个线程,线程有时候也被称为轻量级进程.

一个Java虚拟机的实例运行在一个单独的进程中,不同的线程共享Java虚拟机进程所属的堆内存。这也是为什么不同的线程可以访问同一个对象。线 程彼此共享堆内存并保有他们自己独自的栈空间。这也是为什么当一个线程调用一个方法时,他的.局部变量可以保证线程安全。但堆内存并不是线程安全的,必须通 过显示的声明同步来确保线程安全。

问题:列举几种不同的创建线程的方法.

解答:可以通过如下几种方式:

•  继承Thread 类

•  实现Runnable 接口

•  使用Executor framework (这会创建一个线程池)

123456789101112131415classCounter extendsThread {     //method where the thread execution will start     publicvoidrun(){        //logic to execute in a thread        }     //let’s see how to start the threads    publicstaticvoidmain(String[] args){       Thread t1 = newCounter();       Thread t2 = newCounter();       t1.start();  //start the first thread. This calls the run() method.       t2.start(); //this starts the 2nd thread. This calls the run() method.      }}123456789101112131415classCounter extendsBase implementsRunnable{     //method where the thread execution will start     publicvoidrun(){        //logic to execute in a thread        }     //let us see how to start the threads    publicstaticvoidmain(String[] args){         Thread t1 = newThread(newCounter());         Thread t2 = newThread(newCounter());         t1.start();  //start the first thread. This calls the run() method.         t2.start();  //this starts the 2nd thread. This calls the run() method.      }}

通过线程池来创建更有效率,

问题:推荐通过哪种方式创建线程,为什么?

解答:最好使用Runnable接口,这样你的类就不必继承Thread类,不然当你需要多重继承的时候,你将 一筹莫展(我们都知道Java中的类只能继承自一个类,但可以同时实现多个接口)。在上面的例子中,因为我们要继承Base类,所以实现Runnable 接口成了显而易见的选择。同时你也要注意到在不同的例子中,线程是如何启动的。按照面向对象的方法论,你应该只在希望改变父类的行为的时候才去继承他。通 过实现Runnable接口来代替继承Thread类可以告诉使用者Counter是Base类型的一个对象,并会作为线程执行。

问题:简要的说明一下高级线程状态.

解答:下图说明了线程的各种状态.

上一篇:返家安全协议下一篇:关汉卿《一枝花·杭州景》赏析