Java学习之多线程

1.继承Thread 类方式创建多线程

创建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();
     }
}

2.Runnable接口方式创建多线程

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);
    }
}

3.多线程的管理

3.1 线程调度

3.1.1 优先抢占式调度

当线程优先级不同时,为保证优先级最高的线程先运行而采用优先抢占式调度算法。即优先级最高的线程优先抢占CPU。

3.1.2 轮转调度

当若干个线程具有相同的优先级时,可采用队列轮转调度法,即当一个线程运行结束时,该优先队列中排在最前面的线程运行。

3.2 线程优先级

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()方法进入就绪状态,但是由于优先级最高,因此是最先执行的。

3.3 线程同步

由于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

3.3 线程组

Java系统的每个线程都属于某一个线程组。采用线程组结构以后,可以对多个线程进行集中管理。如:同时启动,挂起或终止一个线程组中的全部线程。 Java系统专门在java.lang包中提供了ThreadGroup类来实现对线程组的管理功能。 大多数情况下,一个线程属于哪个线程组是由编程人员在程序中指定的,若编程人员没有指定,则java系统会自动将这些线程归于“main”线程组。main线程组是java系统启动时创建的。 一个线程组不仅可以包含多个线程,而且线程组中还可以包含其他的线程组,构成树形结构。 一个线程可以访问本线程组的有关信息,但无法访问本线程组的父线程组。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券