创建Thread类的子类时,首先声明子类的构造方法,其次用定义的run()
方法覆盖Thread类的run()
方法,即将自己要执行的程序区块写入run()
方法中。
class ex_Thread extends Thread{
int pauseTime;
String name;
public ex_Thread(int hTime,String hStr){
pauseTime=hTime;
name=hStr;
}
public void run(){
Calendar now;//Calendar是Java系统提供的日期时间类的类型标识符
int year,month,data,hour,minute,secend;
for (int i=1;i<10;i++){
try {
now=Calendar.getInstance();//取系统时间
year=now.get(Calendar.YEAR);//取年
month=now.get(Calendar.MONTH)+1;//取月
data=now.get(Calendar.DATE);//取日期值
hour=now.get(Calendar.HOUR_OF_DAY);//取小时值
minute=now.get(Calendar.MINUTE);//取分
secend=now.get(Calendar.SECOND);//取秒
//显示时间
System.out.println(" "+name+"时间:"+year+"年"+month+"月"+data+"日"+hour+"小时"+minute+"分"+secend+"秒");
Thread.sleep(pauseTime);
}catch (Exception e){
System.out.println("线程错误"+e);
}
}
}
public static void main(String args[]){
//A线程执行一次后睡眠2000毫秒
ex_Thread myThread1=new ex_Thread(2000,"线程A");
myThread1.start();
//B线程执行一次后睡眠1000毫秒
ex_Thread myThread2=new ex_Thread(1000,"线程B");
myThread2.start();
}
}
Runnable接口只有一个方法run()
,用户新建线程的操作就由这个方法决定。run()
方法必须由实现此接口的类来实现。定义好run()
方法后,当用户程序需要建立新线程时,只要以这个实现了run()
方法的类为参数创建系统类Thread的对象,就可以把用户实现的run()
方法继承过来。
//主程序
public class ex_Thread1 extends JApplet {
public void init(){
//得到窗口容器对象
Container cp=getContentPane();
CString pa=new CString();//创建JPanel对象
CSquare pal=new CSquare();//创建JPanel类的对象
pa.setPreferredSize(new Dimension(300,150));
pa.setBackground(Color.cyan);//设置pa的对象背景颜色
pal.setPreferredSize(new Dimension(300,150));
pal.setBackground(Color.cyan);//设置pal的对象背景颜色
//cp容器的布局为BorderLayout,添加pa及pal的对象到cp容器中
cp.add(pa,BorderLayout.NORTH);
cp.add(pal,BorderLayout.SOUTH);
}
}
//实现屏幕矩形框走动的线程程序
public class CSquare extends JPanel implements Runnable {
int x1,y1,w1,h1;
Thread th2=new Thread(this);
public CSquare(){
x1=5;y1=100;w1=40;h1=40;
start();
}
private void start(){
th2.start();
}
public void run(){
while(true){
x1=x1+5;
if (x1==250)
x1=0;
repaint();//repaint()方法调用paint()方法重画矩形框
try {
Thread.sleep(500);//使th2线程睡眠500ms
}catch (InterruptedException e){}
}//while
}
public void paint(Graphics g){
super.paint(g);
Graphics2D g2=(Graphics2D) g;
Rectangle2D.Double rec1=new Rectangle2D.Double(x1,y1,w1,h1);
g2.draw(rec1);
}
}
//实现屏幕上的字符"Hello Java"走动的线程程序
public class CString extends JPanel implements Runnable {
int x=10,y=50;
String Message="Hello Java";//字符串对象
Font f=new Font("TimesRoman",Font.BOLD,24);//创建字体对象
Thread th1=new Thread(this);
public CString(){
start();
}
private void start(){
th1.start();
}
public void run(){
while (true){
x=x-5;
if (x==0)
x=300;
repaint();//调用paint()方法重画字符串
try {
Thread.sleep(500);//使线程睡眠500ms
}catch (InterruptedException e){}
}//while
}//run
public void paint(Graphics g){
super.paint(g);
Graphics2D g2=(Graphics2D) g;
g2.setFont(f);//设置字体
g2.drawString(Message,x,y);
}
}
当线程优先级不同时,为保证优先级最高的线程先运行而采用优先抢占式调度算法。即优先级最高的线程优先抢占CPU。
当若干个线程具有相同的优先级时,可采用队列轮转调度法,即当一个线程运行结束时,该优先队列中排在最前面的线程运行。
Java线程的优先级是在1~10之间的正整数,数值越大,优先级越高。未设定优先级的线程其优先级取缺省值5。Java线程的优先级设置遵从下列规则:
setPriority()
方法改变线程的优先级。MIN_PRIORITY
表示优先级为1,NORM_PRIORITY
表示优先级为5,MAX_PRIORITY
表示优先级为10。其他级别的优先级可以直接用1~10之间的整数来设置,也可以标识符常量的基础上加一个常数。例如setPriority(Thread.NORM_PRIORITY+3)
优先级为8。class ThreadPriority{
public static void main(String[] args) {
Thread first=new myThread("A线程");//创建A线程
first.setPriority(Thread.MIN_PRIORITY);//设置A线程优先级为1
Thread second=new myThread("B线程");//创建B线程
second.setPriority(Thread.NORM_PRIORITY+1);//设置B线程优先级为6
Thread third=new myThread("C线程");//创建C线程
third.setPriority(Thread.MAX_PRIORITY);//设置C线程优先级为10
first.start();
second.start();
third.start();
}
}
class myThread extends Thread {
String message;
myThread(String message){
this.message=message;
}
public void run(){
for (int i=0;i<2;i++)
System.out.println(message+" "+getPriority());
}
}
可以看出,虽然线程C在程序中最后调用start()方法进入就绪状态,但是由于优先级最高,因此是最先执行的。
由于Java支持多线程,并具有并发的功能,大大提高了计算机的处理能力。在各线程之间不存在共享资源的情况下,几个线程的执行顺序是随机的。但是,当两个或两个以上线程需要共享同一资源时,线程之间的执行次序就需要协调,并且在某个线程占用这一资源时,其他线程只能等待。
如生产者和消费者问题。为了不发生混乱,规定:生产者往货架上放货物时不允许消费者在取走货物,当消费者取走货物时不允许生产者放货物。这种机制在操作系统中称为线程间的同步。在同步机制中,那些访问临界资源的程序段称为临界区。
在Java系统中,临界区程序段用关键字"synchronized"来标注,并通过一个称为监控器的系统软件来管理的。当执行被冠以synchronized
的程序段即临界区程序时,监控器将这段程序(访问的临界资源)加锁,此时,称线程占有临界资源,直到这段程序执行完,才释放锁。只有锁被释放后,其他程序才可以访问这些临界资源。
定义临界区的语句:synchronized (expression) statement
expression 代表类名,是可选项。statement可以是一个方法,也可以是一个语句或一个语句块,最常见的是一个方法。
public class ThreadTB {
public static void main(String[] args) {
HoldInt h=new HoldInt();//h为监控器
ProduceInt p=new ProduceInt(h);
ConsumeInt c=new ConsumeInt(h);
p.start();
c.start();
}
}
class HoldInt{
private int sharedInt;
private boolean writeAble=true;//writeAbke=true表示生产者线程能生产新数据
public synchronized void set(int val){//临界区程序段,也称为同步方法
while (!writeAble){//生产者线程不能生产新数据时进入等待
try{wait();}
catch(InterruptedException e){}
}//生产者被唤醒后继续执行下面的语句
writeAble=false;
sharedInt=val;
notify();
}
public synchronized int get(){//同步方法
while (writeAble){//消费者线程不能消费数据时进入等待状态
try{wait();}
catch (InterruptedException e){}
}//消费者被唤醒后继续执行下面语句
writeAble=true;
notify();
return sharedInt;
}
}
//ProduceInt 是生产者线程
class ProduceInt extends Thread{
private HoldInt hi;
public ProduceInt(HoldInt hiForm){
hi=hiForm;
}
public void run(){
for (int i=1;i<=4;i++){
hi.set(i);
System.out.println("产生的新数据是:"+i);
}
}
}
//ConsumeInt是消费者线程
class ConsumeInt extends Thread{
private HoldInt hi;
public ConsumeInt(HoldInt hiForm){
hi=hiForm;
}
public void run(){
for (int i=1;i<=4;i++){
int val=hi.get();
System.out.println("读到的数据是:"+val);
}
}
}
运行结果如下:
产生的新数据是:1 读到的数据是:1 读到的数据是:2 产生的新数据是:2 读到的数据是:3 产生的新数据是:3 读到的数据是:4 产生的新数据是:4
Java系统的每个线程都属于某一个线程组。采用线程组结构以后,可以对多个线程进行集中管理。如:同时启动,挂起或终止一个线程组中的全部线程。 Java系统专门在java.lang包中提供了ThreadGroup类来实现对线程组的管理功能。 大多数情况下,一个线程属于哪个线程组是由编程人员在程序中指定的,若编程人员没有指定,则java系统会自动将这些线程归于“main”线程组。main线程组是java系统启动时创建的。 一个线程组不仅可以包含多个线程,而且线程组中还可以包含其他的线程组,构成树形结构。 一个线程可以访问本线程组的有关信息,但无法访问本线程组的父线程组。