从《UNIX环境高级编程》知,多线程这个概念出现不是很久,作为现代IT的发展,面向对象的编程语言不可能不支持多线程。
所谓的多线程,可以简单的理解为同一个时段,有多个子任务"同时"运行,但要注意其与多进程的区别——或者最重要的一点,要牢记多进程比多线程切换的开销小得多。
多线程是指同时存在几个执行体,按几条不同的执行线索共同工作的情况。Java语言实现了对多线程的支持,它使得编程人员可以很方便地开发出能同时处理多个任务的功能强大的应用程序。
在Java语言中,不仅语言本身有多线程的支持,可以方便地生成多线程的程序,而且运行环境也利用多线程的应用程序并发提供多种服务。
一、线程与进程的区别:
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
1.新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源,并已被初始化。(注意:说的是此时就已有了内存空间和其它资源)
2.就绪
处于新建状态的线程被启动后,将进入线程队列排队等待CPU时间片,此时它已经具备了运行的条件,一旦轮到它来享用CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。另外,原来处于阻塞状态的线程被解除阻塞后也将进入就绪状态。
3.运行
当就绪状态的线程被调度并获得处理器资源时,便进入运行状态。
run方法
每一个Thread类及其子类的对象都有一个重要的run()方法,当线程对象被调度执行时,它将自动调用本对象的run()方法,从第一句开始顺序执行。run()方法定义了这一类线程的操作和功能。
4.阻塞
一个正在执行的线程如果在某些特殊情况下,如被人为挂起或需要执行费时的输入输出操作时,将让出CPU并暂时中止自己的执行,进入阻塞状态。
阻塞时它不能进入排列队列,只有当引起阻塞的原因被消除时,线程才可以转入就绪状态,重新进到线程队列中排队等待CPU资源,以便从原来终止处开始继续执行。
5.死亡
处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有两个:
一个是正常运行的线程完成了它的全部工作,即执行完了run()方法的最后一个语句并退出;
另一个是线程被提前强制性地终止,如通过执行stop()方法或destroy()终止线程。
三、线程调度与优先级
处于就绪状态的线程排队等候处理器资源
线程先分配CPU资源的先后,称为线程调度
为了方便线程调度,多线程系统会给每个线程自动分配一个线程的优先级,任务较紧急重要的线程,其优先级就较高;相反则较低
在Java系统中,线程调度采用优先级基础上的“先到先服务”原则
四、线程组————————————这个,在C/C++中没有,不过,话说回来,多线程、多进程的概念本身就是操作系统中引进的,不是编程语言的内容,不过,编程语言倒应该提供机制。
在Java中,线程组是类ThreadGroup的对象,每个线程Thread都隶属于惟一一个线程组
这个线程组在线程创建时指定并在线程的整个生命期内都不能更改
用户可以通过调用包含 ThreadGroup 类型参数的 Thread 类构造函数来指定线程所属的线程组。(学习:这个就有点像单选按钮组一样,其中,可以在创建Checkbox时,其一种构造函数是指定其属于哪个CheckBoxGroup,其构造函数原型为Checkbox(String,CheckBoxGroup,boolean)
在创建线程时显式地制定线程组,采用下述三种构造方法之一:
1)Thread(ThreadGroup,Runnable)
2)Thread(ThreadGroup,String)
3)Thread(ThreadGroup,Runnable,String)
若没有指定,则线程默认地隶属于名为system的系统线程组
线程组的作用:
Java允许对一个线程组中的所有线程同时进行操作,比如可以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程
Java的线程组机制的另一个重要作用是线程安全。线程组机制允许通过分组来区分有不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来支持不对等安全措施的采用
对线程的综合支持是Java技术的一个重要特色.它提供了thread类、监视器和条件变量的技术.
虽然Macintosh,Windows NT,Windows 9等操作系统支持多线程,但若要用C或C++编写多线程程序是十分困难的,因为它们对数据同步的支持不充分.
Java中编程实现多线程应用有两种途径:
创建用户自己的线程子类,
在用户自己的类中实现Runnable接口,注意————Runnable是一个接口
Thread类
1、构造函数
public Thread();这个方法创建了一个默认的线程类的对象。
Public Thread(Runnable target);这个方法在上一个构造函数的基础上,利用一个实现了Runnable接口参数对象Target中所定义的run()方法,以便初始化或覆盖新创建的线程对象的run()方法。
public Thread(String name);利用一个String类的对象name为所创建的线程对象指定了一个名称
Public Thread(ThreadGroup group, Runnable target)这个方法利用给出的ThreadGroup类的对象为所创建的线程指定了所属的线程组。
public Thread(ThreadGroup group, String name);
这个方法在第三个构造函数创建了一个指定了一个字符串名称的线程对象的基础上,利用给出的ThreadGroup类的对象为所创建的线程指定了所属的线程组。
public Thread(ThreadGroup group, Runnable target , String name);这个方法综合了上面提到的几种情况,创建了一个属于group的线程组,用target对象中的run()方法初始化了本线程中的run()方法,同时还为线程指定了一个字符串名
2、优先级
Thread类有三个有关线程优先级的静态常量:
public static final int MAX_PRIORITY
public static final int MIN_PRIORITY
public static final int NORM_PRIORITY
对应一个新建线程,系统会根据如下的原则为其自定义的优先级:
新建线程将继承创建它的父线程的优先级。
一般情况下,主线程具有普通优先级。
另外,用户可以通过调用Thread类的方法setPriority()来修改系统自动设定的线程优先级,使之符合程序的特定需要
Runnable接口
Runnable接口只有一个方法run(),所有实现Runnable接口的用户类都必须具体实现这个run()方法
当线程被调度并转入运行状态时,它所执行的就是run()方法中规定的操作。
一个实现Runnable接口的类实际上定义了一个主线程之外新线程的操作,
创建线程的方法:
1. 继承类Thread
public class mythread extends Thread
2. public class mythread extends Applet implements Runnable————————实现Runnable接口
(小应用或已经是某个类的子类时)
3. 上述两种方法中都可用类Thread产生线程的对象 Thread newthread;
4. 创建并启动线程
newthread=new Thread(this);
newthread.start();
5. run方法是运行线程的主体,启动线程时,由java直接调用 public void run()
6.停止线程,调用线程的stop() newthread.stop()
7 sleep方法的作用,暂停线程的执行,让其它线程得到机会,sleep要丢出异常,必须抓住.
Try{sleep(100)}catch(InterruptedException e){}
8.其它常用的方法
isAlive :判断线程目前是否正在执行状态中
if(newthread.isAlive()) newthread.stop();
resume:要求被暂停的线程继续执行
suspend:暂停线程的执行
join:等待线程执行完毕
thatThread.join();被等待的那个线程不结束,当前线程就一直等待.
yield:将执行的权力交给其它线程,自己到队列的最后等待.
多线程并发程序
如前所述,在程序中实现多线程并发程序有两个途径:
一是创建Thread类的子类;
另一个是实现Runnable接口。
程序员应该控制的关键性操作有两个:
定义用户线程的操作,即定义用户线程中的run()方法。
在适当的时候建立用户线程并用start()方法启动线程,如果需要,还要在适当的时候休眠或挂起线程。
多线程的同步
一、什么是线程同步问题
在多线程的程序中,当多个线程并发执行时,由于线程的相对执行顺序是不确定的。
当多个并发线程需要共享程序的代码区域和数据区域时,由于各线程的执行顺序是不确定的,因此执行的结果就带有不确定性,这就要求线程同步
二、临界区和线程同步
在多线程程序设计中,我们将程序中那些不能被多个线程并发执行的代码段称为临界区
当某个线程已处于临界区时,其他的线程就不允许再进入临界区
实现方法:则是在共享代码之前加入synchronized段,把共享代码包含在synchronized段中,格式如下:
synchronized[(objectname)] statement
其中,objectname用于指出该临界区的监控对象,是可选项;
statement为临界区,它既可以是一个方法,称为同步方法,也可以是一段程序代码,称为同步语句块
下列语句定义了一个同步方法method1()
synchronized int method1(){
……
}
下列语句定义了一个同步语句块
int method1(){
synchronized(this){
……
}
}
三、PV操作(wait方法和notify方法)
1.wait()方法
wait()方法用于使当前线程放弃临界区而处于睡眠状态,直到有另一线程调用notify()方法将它唤醒或睡眠时间已到为止,其格式如下:
wait();
wait(millis);
其中millis是睡眠时间
2.notify()方法
notify()方法用于将处于睡眠状态的某个等待当前对象监控器的线程唤醒。
如果有多个这样的线程,则按照先进先出的原则唤醒第一个线程。
Object类中还提供了另一个方法notifyAll(),用于唤醒所有因调用wait()方法而睡眠的线程。
五、死锁
死锁是指两个或多个线程无休止地互相等待对方释放所占据资源的过程。错误的同步往往会引起死锁。为了防止死锁,在进行多线程程序设计时必须遵循如下原则:
1)在指定的任务真正需要并发时,才采用多线程来进行程序设计。
2)在对象的同步方法中需要调用其他同步方法时必须小心。
3)在临界区中的时间应尽可能短,需要长时间运行的任务尽量不要放在临界区中。
import java.io.*;
public class ThreadTest{
public static void main(String[] argv){
if(argv.length<1){
System.out.println("请输入一个命令行参数");
System.exit(1);
}
primeThread myPrime=new primeThread(Integer.parseInt(argv[0]));
myPrime.start();
while(myPrime.isAlive()&&myPrime.ReadyToGoOn()){
System.out.println("Counting the prime number...\n");
try{
Thread.sleep(500);
}catch(Exception e){
return;
}
}
myPrime.stop();
}
}
class primeThread extends Thread{
boolean m_continue=true;
int m_circlenum;
primeThread(int num){
m_circlenum=num;
}
boolean ReadyToGoOn(){
return m_continue;
}
public void run(){
int number=3;
boolean flag=true;
while(true){
synchronized(this){//注意:请读者在第一次运行这个代码时,将这段synchronized去掉试试,加上后又再试试,比较两次结果的不同,并想想为什么
for(int i=2;i<number;i++){
if(number%i==0)
flag=false;}//这里的花括号与synchronized那段对应
if(flag)
System.out.println(number + " is a prime!");
else
System.out.println(number + " is not a prime!");
number++;
if(number>m_circlenum)
m_continue=false;
flag=true;
try{
sleep(500);
}catch(Exception e){
return ;
}
}
}
}
}
下面是个时钟程序,感觉比较经典,也在这里共享下:
import java.util.*;
import java.awt.*;
import java.applet.*;
import java.text.*;
public class Clock extends Applet implements Runnable {
private volatile Thread timer; // 用来显示时间的子线程
private int lastxs, lastys, lastxm,
lastym, lastxh, lastyh; // 用来表示种上各针的位置
private SimpleDateFormat formatter; // 用于格式化显示的时间
private String lastdate; // 用于显示时间的字符串
private Font clockFaceFont; // 字体对象
private Date currentDate; // 时间对象
private Color handColor; // 表示种臂的颜色的颜色对象
private Color numberColor; // 秒针和时间显示的颜色
private int xcenter = 80, ycenter = 55; // 中央位置
public void init() { //用户Applet的初始化
int x,y;
lastxs = lastys = lastxm = lastym = lastxh = lastyh = 0;
formatter = new SimpleDateFormat ("EEE MMM dd hh:mm:ss yyyy",
Locale.getDefault());
currentDate = new Date();
lastdate = formatter.format(currentDate);
clockFaceFont = new Font("Serif", Font.PLAIN, 14);
handColor = Color.blue;
numberColor = Color.darkGray;
try { // 设置窗体背景色和时针的颜
setBackground(new Color(Integer.parseInt(getParameter("bgcolor"),
16)));
} catch (NullPointerException e) {
} catch (NumberFormatException e) {
}
try {
handColor = new Color(Integer.parseInt(getParameter("fgcolor1"),
16));
} catch (NullPointerException e) {
} catch (NumberFormatException e) {
}
try {
numberColor = new Color(Integer.parseInt(getParameter("fgcolor2"),
16));
} catch (NullPointerException e) {
} catch (NumberFormatException e) {
}
resize(300,300); // 设置窗体的大小
}
public void update(Graphics g) {
int xh, yh, xm, ym, xs, ys;
int s = 0, m = 10, h = 10;
String today;
currentDate = new Date();
formatter.applyPattern("s"); //分别得到当前的时,分,秒
try {
s = Integer.parseInt(formatter.format(currentDate));
} catch (NumberFormatException n) {
s = 0;
}
formatter.applyPattern("m");
try {
m = Integer.parseInt(formatter.format(currentDate));
} catch (NumberFormatException n) {
m = 10;
}
formatter.applyPattern("h");
try {
h = Integer.parseInt(formatter.format(currentDate));
} catch (NumberFormatException n) {
h = 10;
}
// 设置时钟各针的结束位置
xs = (int) (Math.cos(s * Math.PI / 30 - Math.PI / 2) * 45 + xcenter);
ys = (int) (Math.sin(s * Math.PI / 30 - Math.PI / 2) * 45 + ycenter);
xm = (int) (Math.cos(m * Math.PI / 30 - Math.PI / 2) * 40 + xcenter);
ym = (int) (Math.sin(m * Math.PI / 30 - Math.PI / 2) * 40 + ycenter);
xh = (int) (Math.cos((h*30 + m / 2) * Math.PI / 180 - Math.PI / 2) * 30
+ xcenter);
yh = (int) (Math.sin((h*30 + m / 2) * Math.PI / 180 - Math.PI / 2) * 30
+ ycenter);
// 得到用于显示的时间
formatter.applyPattern("EEE MMM dd HH:mm:ss yyyy");
today = formatter.format(currentDate);
g.setFont(clockFaceFont);
// 去掉上次显示的信息
g.setColor(getBackground());
if (xs != lastxs || ys != lastys) {
g.drawLine(xcenter, ycenter, lastxs, lastys);
g.drawString(lastdate, 5, 125);
}
if (xm != lastxm || ym != lastym) {
g.drawLine(xcenter, ycenter-1, lastxm, lastym);
g.drawLine(xcenter-1, ycenter, lastxm, lastym);
}
if (xh != lastxh || yh != lastyh) {
g.drawLine(xcenter, ycenter-1, lastxh, lastyh);
g.drawLine(xcenter-1, ycenter, lastxh, lastyh);
}
// 画更新的时间和种面
g.setColor(numberColor);
g.drawString(today, 5, 125);
g.drawLine(xcenter, ycenter, xs, ys);
g.setColor(handColor);
g.drawLine(xcenter, ycenter-1, xm, ym);
g.drawLine(xcenter-1, ycenter, xm, ym);
g.drawLine(xcenter, ycenter-1, xh, yh);
g.drawLine(xcenter-1, ycenter, xh, yh);
lastxs = xs; lastys = ys;
lastxm = xm; lastym = ym;
lastxh = xh; lastyh = yh;
lastdate = today;
currentDate = null;
}
public void paint(Graphics g) { //重绘窗体的paint方法
g.setFont(clockFaceFont);
g.setColor(handColor); //描绘时钟的种环和刻度
g.drawArc(xcenter-50, ycenter-50, 100, 100, 0, 360);
g.setColor(numberColor);
g.drawString("9", xcenter-45, ycenter+3);
g.drawString("3", xcenter+40, ycenter+3);
g.drawString("12", xcenter-5, ycenter-37);
g.drawString("6", xcenter-3, ycenter+45);
g.setColor(numberColor); //用来描绘时间和种上的各针
g.drawString(lastdate, 5, 125);
g.drawLine(xcenter, ycenter, lastxs, lastys);
g.setColor(handColor);
g.drawLine(xcenter, ycenter-1, lastxm, lastym);
g.drawLine(xcenter-1, ycenter, lastxm, lastym);
g.drawLine(xcenter, ycenter-1, lastxh, lastyh);
g.drawLine(xcenter-1, ycenter, lastxh, lastyh);
}
public void start() { //时钟线程的启动
timer = new Thread(this); //用当前对象为参数创建线程
timer.start();
}
public void stop() { //时钟线程的灭亡
timer = null;
}
public void run() { //时钟线程的操作
Thread me = Thread.currentThread();
while (timer == me) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
}
repaint(); //休眠一段时间后重绘窗体
}
}
public String[][] getParameterInfo() {//初始化定义一些颜色
String[][] info = {
{"bgcolor", "hexadecimal RGB number",
"The background color. Default is the color of your browser."},
{"fgcolor1", "hexadecimal RGB number",
"The color of the hands and dial. Default is blue."},
{"fgcolor2", "hexadecimal RGB number",
"The color of the second hand and numbers. Default is dark gray."}
};
return info;
}
}
之前对/其中时针针臂的计算还未理解清楚,现在明白是:首先,要注意,这里的m是表示的是此刻的分钟,而不是分针位置,然后,再想到,应是h*60+m得总分钟数,而总共12个小时时刻对应12*60分钟,因此,整个占(h*60+m)/(12*60)分之2Pi
<html>
<h1>This is an example of Thread to show Clock</h1>
<applet code="Clock" width="400" height="400"></applet>
</html>