http://www.cnblogs.com/baoguo/articles/826534.html
Java中的多线程编程
主要内容 初识Java多线程 线程的生命周期 Java多线程程序设计方法 多线程应用示例 线程的优先级、同步* |
初识多线程
一个简单的多线程的示例
class ManyThread extends Thread{
int n=0;
public static void main(String[] aa){
ManyThread t1=new ManyThread();
ManyThread t2=new ManyThread();
System.out.println("Thread1 start!");
t1.start();
System.out.println("Thread2 start!");
t2.start();
System.out.println("Ok");
}
public void run(){
while(n<10){
n++;
System.out.println(currentThread().getName()+"/t"+n);
}
}
}
结论:
² 线程类名为Thread,可由Thread及其子类创建线程对象。
² 可通过Thread.currentThread()可测得当前线程
² 线程是由start启动的,启动后会自动执行run方法。
单线程和多线程
线程:一个程序中彼此分离的、能独立运行的子任务。
单线程和排队等候
没有特意创建线程的程序为单线程程序, Java能自动创建和控制线程。
单线程的本质是排队等候,一个任务必须在另一个任务之后执行。
资源利用率低,CPU在大多时候空闲 |
多线程和并行执行
多线程的程序将任务分成几个子任务,多个子任务并行操作。(抢占式)
思考 1. 多线程程序的运行方式是? 2. 一个任务运行完毕,再执行另一个任务,这是单线程还是多线程? 3. 多线程的程序有什么优越之处? 4. 哪种程序有更好的用户响应能力? 5. 线程是如何启动的? 6. 线程启动后具体执行的方法是? 7. 在多线程工作时,怎么测得当前占据CPU运行时刻的线程? |
线程的生命周期
在一个线程的生命周期中,它总处于某一种状态中。
Thread类及其主要方法
构造方法
public Thread():用缺省名称创建一个Thread对象
public Thread(String name):用指定名称创建一个Thread对象
线程的常用方法
说明 | 任务 |
public void start() | 启动一个线程 |
public void run() | 执行线程 |
| 休眠 |
public void stop() | 停止线程 |
public static Thread currentThread() 必须throws InterruptedException | 返回当前线程 |
public String getName() | 返回线程的名称 |
线程的状态
线程的状态通常可归纳为:新生态、运行态、不可运行态、死亡态四种状态。
新生态
时机:当创建了类Thread或其子类的实例对象时,一个新的线程就产生了。
ManyThread t1=new ManyThread()
线程去向:只能启动或终止该线程。调用start()和stop()之外的其它方法都会失败并且会引起非法状态处理。
运行态( Runnable )
时机:对新生态的线程执行start()方法进入运行态,此时该线程处于可运行状态。
t1.start();
线程去向:进入不可运行的状态(等待或睡眠)和死亡态。
说明:
² 处于运行态的线程都在运行或侍机运行。
² 单处理器的计算机,每一时刻真正处于运行中的线程只有一个。
² JavaVM实现调度来保证这些线程共享处理器。
不可运行态
时机:对运行态中的线程进行了如下处理后,线程处于不可运行状态。
² 调用了sleep()方法;
² 调用了suspend()方法(线程被挂起);
² 为等候一个条件变量,线程调用wait()方法;
² 输入输出流中发生线程阻塞。
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e){}
线程去向:进入运行态或死亡态。
四种不可运行态有不同的方式回到运行态:
sleep()——方法中的参数为睡眠时间,时间一到,线程即为可运行的;
suspend()——调用resume()方法来返回;
wait()——该条件变量所在的对象调用notify()或notifyAll()方法;
在I/O流中发生线程阻塞——待特定的I/O指令结束,自动回到可运行态。
死亡态
时机:对线程执行stop()方法,或线程运行结束,即进入死亡态。
说明:可用线程对象=null;代替线程对象.stop();
多线程程序设计
由Thread继承类创建线程
程序框架
class NewThread extends Thread{ //对应一个任务
……
public void run(){
}
}
……
class ThreadTest{
public static void main(String[] aa){
NewThread t=new NewThread();
t.start();
……
}
}
要点:
² 定义一个继承Thread的子类,重写 run()方法,实现一个子任务。
² main方法中,实例化子类,创建一个线程对象,并调用start()方法执行子任务。
例:阅读和分析程序。
class ThreadTest extends Thread{
int n=0;
int count=0;
ThreadTest(int i){
count=i;
start();
}
public void run(){
while(true){
System.out.print(count+" ");
if(n++>15)return;
// yield();
}
}
public static void main(String[] aa){
for(int i=0;i<5;i++)
new ThreadTest(i);
}
}
得到结论:
² 主线程的执行位置就是main。
² 每个线程执行其代码的方式都是顺序执行的。
² 一个线程执行其代码与其他线程相互独立。
进一步的理解:
² 线程对于cpu时间片的交替占有特性。
² 用yield()方法,让当前线程作出让步。
public static void yield()
² 用sleep()可让线程休眠一定的时刻(毫秒数)。
public static void sleep(long millis)
分析和回答
1. 该程序启动了多少个线程? 2. 线程的执行机会是顺序均匀的吗? 3. 让线程作出让步的方法是? 4. 让线程休眠的方法是? 5. 哪条语句决定一个线程运行结束? 6. ThreadTest的构造方法中执行start意在? |
用Runnable接口创建线程
Runnable接口继承自Thread,其中只有一个run()方法。
察看Thread的其他构造方法
public Thread(Runnable target)
public Thread(Runnable target,String name)
通过Runnable创建线程的步骤:
² 定义一个实现了Runnable的类,其中重写run方法说明任务。
² 通过new Thread(Runnable子类的实例对象)创建线程。
练习1:利用Runable改写上例。
练习2:用多线程模拟多处卖票的问题(假定总共有100张票,有三个卖票窗口)。
class SellTicket implements Runnable{
static int num=1;
public void run(){
}
public static void main(String[] aa){
}
}
比较:多线程设计时,Runnable接口更具灵活性。一个用户类可以在继承别的类的基础上实现该接口。
思考 1. 该程序启动了多少个线程? 2. 线程的执行机会是顺序均匀的吗? 3. 让线程作出让步的方法是? 4. 让线程休眠的方法是? 5. 哪条语句决定一个线程运行结束? 6. ThreadTest的构造方法中执行start意在? |
多线程应用举例
一个时钟Applet示例
设计如下图示的时钟,可以逐秒更新显示。
设计分析
类Clock继承JApplet,并实现Runnable接口,通过一个线程来实现时间的更新显示。
其中各方法的主要任务:
init()——
paint(Graphics g) ——获取当前日历,显示时间
run()——线程的运行(隔一秒刷新)
取得系统时间的方法:
Calendar now = Calendar.getInstance();
利用其中的get方法获取HOUR、MINUNT、SECOND
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class Click extends JApplet implements Runnable{
Font f=new Font("",0,50);
public void init(){
//创建并运行线程
}
public void paint(Graphics g){
g.clearRect(0,0,this.getWidth(),this.getHeight());
g.setFont(f);
g.setColor(Color.green);
g.drawString(getTime(),50,50);
}
public void run(){
//线程的运行(隔一秒刷新)
}
public String getTime(){
Calendar now=Calendar.getInstance();
//取得系统时间
}
}
练习:将上例改写为Java Application。
一个飞行动画Applet
飞船图片文件名:Rocketship.gif
线程的优先级和线程调度
单CPU的计算机在执行多线程程序时需进行线程调度。
线程优先级的设置
public final void setPriority(int newPriority)
参数:可选线程中的三个静态变量
² MAX_PRIORITY(高优先级)10
² MIN_PRIORITY(低优先级)1
² NORM_PRIORITY(缺省的优先级)5
Java采用的是抢占式的调度方式,高优先级的线程进入可运行(runnable)状态时,会抢占低优先级的线程的位置。
例:观察下列程序的执行情况,回答问题。
class ThreadTest{
public static void main(String[] aa){
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setPriority(5);
mt2.setPriority(10);
mt1.start();
mt2.start();
while(true){
System.out.println(Thread.currentThread().getName());
}
}
}
class MyThread extends Thread{
public void run(){
while(true){
System.out.println(this.getName());
}
}
}
问题:
高优先级线程会绝对占据CPU资源吗?
观察程序运行结果,体会线程优先级的意义
线程的同步
以上所创建和运行的线程都是独立的,而且异步执行:
² 每个线程自含了运行所需要的数据或方法,不需要外部的资源或方法
² 每个线程不关心其它线程的状态或行为。
为什么要同步?
问题:有两个线程thread1、thread2,当它们操作同一个对象时,会发现由于thread1与thread2是同时执行的,因此可能thread1修改了数据而thread2读出的仍为旧数据。
问题原因:资源使用协调不当(不同步)造成的。
解决方法:协调线程,以免共享资源时,线程间发生冲突。——同步!
同步的实现方法
Java提供了同步方法和同步状态来协调资源:
² 同步方法指方法前修饰synchronized关键字
² 同步状态指对象或数据前修饰synchronized关键字
² 被宣布为同步的方法、对象或类数据,在任一时刻只能被一个线程使用
同步方法
同步方法:用synchronized修饰的方法
作用:同步方法当一个线程执行时自动加锁,直到方法运行结束锁解除。
例:阅读程序,理解同步方法
class SynchTest implements Runnable{
public static void main(String args[]){
SynchTest st=new SynchTest();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();
t2.start();
while((t1.isAlive())||(t2.isAlive()));
System.out.println("The test is end.");
}
public void run(){
String name=Thread.currentThread().getName();
show(name);
System.out.println(name+" is dead!");
}
private synchronized void show(String name){
for(int j=1;j<=10;j++){
System.out.println("I am "+name+"-- I have run "+j+" times.");
}
}
}
关键字synchronized会告诉JVM在方法运行时需要一个锁,然后 JVM会创建并管理锁:
² 线程可自动请求锁
² 除非某个线程已经申请并获准使用锁,否则很容易申请到锁
² 当同步方法结束后会自动释放锁
同步块
同步块:用synchronized(Object)标识的语句块。
作用:语句块被一个线程执行时会自动加锁,直到运行结束,锁解除。