06 Java多线程机制

Java多线程机制

基本内容

进程:是程序的一次动态执行过程,是从代码加载、执行至执行完毕的一个完整过程。

线程:是比进程更小的执行单位。与进程不同的是,线程的中断与恢复可以更加节省系统的开销。进程和线程间也可以共享进程中的某些内存单元(包括代码与数据)。

主线程(main线程):JVM加载代码发现main方法后会启动一个线程,

如果main方法中没有其他的线程,那么执行完main的最后一个语句后,JVM就会结束Java程序。

如果main中有其他线程,那么线程间轮换,即使执行完最后一条main语句,也不会结束java应用程序,只有程序中所有线程都结束了,才会结束Java应用程序。

线程的状态与生命周期

  1. 新建。声明并创建一个Thread类及其子类的对象后,线程对象就有了内存和资源。
  2. 运行。须调用(从父类继承来的)start()方法通知JVM,这样JVM才知道又有一个新线程排队了。
  3. 中断。
    • 线程进入休眠状态,执行sleep(int millsecond)方法,该方法是Thread类的一个类方法,时间到后就排队等待CPU资源了
    • 等待状态,执行wait()方法,不会主动去排队,必须由其他线程调用notify()方法通知它去排队
  4. 死亡。一般是执行完run()方法中的全部语句就结束了run()方法,另一种是强制性终止。

Speak.java

package com.Thread;

class SpeakElephant extends Thread{
    public void run(){
        for(int i=1;i<=20;i++){
            System.out.println("大象"+i+" ");
        }
    }
}

class SpeakCar extends Thread{
    public void run(){
        for(int i=1;i<=20;i++){
            System.out.println("轿车"+i+" ");
        }
    }
}

public class Speak {
    public static void main(String[] args) {
        SpeakElephant speakElephant = new SpeakElephant(); //创建线程
        SpeakCar speakCar = new SpeakCar();
        speakElephant.start();  //启动线程
        speakCar.start();
        for(int i=1;i<=15;i++){
            System.out.println("主人"+i+" ");
        }
    }
}
主人1 主人2 主人3 主人4 主人5 主人6 主人7 主人8 主人9 主人10 主人11 主人12 
主人13 主人14 主人15 轿车1 轿车2 大象1大象3  大象6 大象8 大象10大象12 大象13 大象14 大象15 大象16 
大象17 轿车3 轿车4 轿车5 轿车6 轿车7 轿车8 轿车9 轿车10 轿车11 轿车12 轿车13 轿车14 轿车15 轿车16 
轿车17 轿车18 轿车19 轿车20 大象18 大象19 大象20 

Process finished with exit code 0

代码分析:执行完main函数中前四条语句后,JVM知道有三条线程,它们间需要轮流使用CPU资源。因此,一般for循环输出完一次输出后,就切换线程。

线程调度与优先级

线程的优先级通常在常数1~10之间,默认为5,一些操作系统只识别3个级别:1、5、10。

JVM的线程调度器是使高优先级的线程能始终运行,然后使用时间片轮转,实际编程中,不提倡使用线程的优先级来保证算法的正确执行。

线程的创建

  • 使用Thread的子类创建,子类需重写run()方法,否则会因为父类run()方法中什么也没有而什么也不做。

​ 优点是:新增成员变量和方法。

  • 使用Thread类直接创建。Thread(Runnable target)创建,target是一个Runnable类型的接口,该实例对象称作所创建线程的目标对象。

    调用start()方法后,就自动调用run()方法

package com.Thread;

class ElephantTarget implements Runnable{
    public void run(){
        for(int i=1;i<=20;i++){
            System.out.println("大象"+i+" ");
        }
    }
}

class CarTarget implements Runnable {
    public void run() {
        for (int i = 1; i <= 20; i++) {
            System.out.println("轿车" + i + " ");
        }
    }
}

public class Speak {
    public static void main(String[] args) {
        Thread speakElephant;   //用Thread声明线程
        Thread speakCar;

        ElephantTarget elephant;  //elephant是目标对象
        CarTarget car;
        elephant = new ElephantTarget();  //创建目标对象
        car = new CarTarget();

        speakElephant = new Thread(elephant);  //创建线程,其目标对象是elephant
        speakCar = new Thread(car);

        speakElephant.start();  //启动线程
        speakCar.start();

        for(int i=1;i<=15;i++){
            System.out.println("主人"+i+" ");
        }
    }
}

使用Runnable接口比使用Thread子类更灵活?

对于Thread(Runnable target)构造方法创建的线程,轮到它来享用CPU资源时,目标对象就会自动调用接口中的run()方法。因此,对于使用同一目标的线程,目标对象的成员变量自然就是这些线程共享的数据单元。例如猫、狗两个线程共享一桶水,即房屋是线程的目标对象,一桶水被猫和狗共享。

Water.java

package com.Thread;

class House implements Runnable{
    int waterAmount;
    public void setWaterAmount(int w){
        waterAmount = w;
    }
    public void run(){
        while(true){
            String name = Thread.currentThread().getName();
            if(name.equals("狗")){
                System.out.println(name+"喝水");
                waterAmount-=2;
            }
            else if(name.equals("猫")){
                System.out.println(name+"喝水");
                waterAmount-=1;
            }
            System.out.println("剩"+waterAmount);
            try{
                Thread.sleep(2000);
            }
            catch(InterruptedException e){}
              return;
        }
    }
}

public class Water {
    public static void main(String[] args) {
        House house = new House();
        house.setWaterAmount(10);
        Thread dog,cat;
        dog = new Thread(house);
        cat = new Thread(house);
        dog.setName("狗");
        cat.setName("猫");
        dog.start();
        cat.start();
    }
}
狗喝水
猫喝水
剩8
剩7

目标对象与线程的关系

  • 完全解耦

    house不包含对cat和dog线程对象的引用,目标对象就需要通过获得线程的名字(因无法获得引用)来确定哪个线程正在使用CPU资源。

  • 弱耦合

    线程作为目标对象的成员。

    上例中的核心代码:

    class House implements Runnable{
        int waterAmount;
        Thread dog,cat;
        House(){
            dog = new Thread(this);
            cat = new Thread(this);
        }
    }
    
    public class Water{
        public static void main(String args[]){
            House house = new House();
            house.setWater(10);
            house.dog.start();
            house.cat.start();
        }
    }
    

线程的常用方法

  • start()

    线程一旦轮到它享用CPU资源,就开始脱离创建它的线程独立开始自己的生命周期。

    只有处于新建状态的线程才能调用start() ,如果start() 后再start() ,会导致IllegalThreadState-Exception异常。

  • run()

    Thread类的和Runnable接口中的run()方法功能和作用相同,用来定义线程对象被调度之后执行的操作,都是系统自动调用而用户程序不得引用的方法。执行完毕后,线程变成死亡状态(线程释放了实体,即内存)。

    同样不赞成run()之后再调用start()方法。

  • sleep()

    由于线程的调度执行是按照优先级的高低顺序执行的,当需要高优先级的线程让出CPU资源时,可执行sleep()方法。

    如果休眠时被打断,会抛出InterruptedException异常,因此必须在try-except中调用sleep方法。

  • isAlive()

    处于新建状态及死亡状态的线程调用isAlive()方法返回false。

    一个已经运行的线程没有进入死亡状态时,不要再给它分配实体,由于线程只能引用最后分配的实体,先前分配的实体就变成了垃圾,且不会被垃圾收集器收集掉,因为JVM认为那个垃圾实体正在“运行”状态,如果突然释放会引起错误甚至设备的毁坏。

    Thread thread = new Thread(target);
    thread.start();
    //如果线程已经进入运行状态,再执行下面语句,线程将会获得新的实体
    thread = new Thread(target);
    
    package com.Thread;
    
    import java.util.Date;
    import java.text.SimpleDateFormat;
    
    class Home implements Runnable {
        int time=0;
        SimpleDateFormat m=new SimpleDateFormat("hh:mm:ss");
        Date date;
        public void run() {
            while(true) {
                date=new Date();
                System.out.println(m.format(date));
                time++;
                try{ Thread.sleep(1000);
                }
                catch(InterruptedException e){}
                if(time==3) {
                    Thread thread=Thread.currentThread();
                    thread=new Thread(this);
                    thread.start();
                }
            }
        }
    }
    
    public class DateThread {
        public static void main(String args[]) {
            Home home=new Home();
            Thread showTime=new Thread(home);
            showTime.start();
        }
    }
    
    10:59:42
    10:59:43
    10:59:44
    10:59:45
    10:59:45
    10:59:46
    10:59:46
    10:59:47
    10:59:47
    10:59:48
    10:59:48
    10:59:49
    10:59:49
    10:59:50
    10:59:50
    //从45开始就开始重复,因为垃圾实体仍在工作!!
    
  • currentThread()

    是Thread类的类方法,可以通过类名调用,方法返回当前正在使用CPU资源的线程。

  • interupt()

    用来吵醒休眠的线程,就是要导致休眠的线程发生InteruptedException异常,从而结束休眠,重新排队。

线程同步

被若干个线程共同使用的方法用synchronized修饰,实例中可以是重写/补充run()方法,run()方法中调用这一被synchronized修饰的方法,这样就能控制线程读取修改某一变量的值时不发生混乱。

协调同步的线程

举例:A、B、C排队买票,A买票后无法找零,则需要等待并允许排在后面的人买票,以便够钱找零。

如果一个线程中使用的同步方法中用到的某个变量,需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法中断本线程的执行。

其他线程如果在使用这个同步方法时不需要等待,在它使用完这个同步方法的同时,应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。按照“先中断的先继续”原则。如果使用notify()方法,那么只是通知某一个进程结束等待

在许多实际问题中wait方法应当放在一个“while(等待条件){}”的循环语句中,而不是if语句中。

如果将wait();改为Thread.sleep(3000); 那么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值