JavaSE之多线程(二)

时间:2022-11-11 18:34:50

这篇文章继续上篇的内容。

四、如何实现多线程程序?

由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后将这些东西封装在类*我们使用。我们就可以实现多线程程序了。那么Java提供的类是什么呢?Thread。通过查看API,我们知道了有2中方式实现多线程程序。

方式1:继承Thread类。

方式2:实现Runnable接口(下次再说喽)

步骤:
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()方法
C:创建对象
D:启动线程

为什么要重写run()方法呢?

不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。所以重写run()方法是为了让程序以多线程的形式执行。

public class MyThread extends Thread {

    @Override
    public void run() {
        // 自己写代码
        // System.out.println("好好学习,天天向上");
        // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
        for (int x = 0; x < 200; x++) {
            System.out.println(x);
        }
    }

}


public class MyThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        // MyThread my = new MyThread();
        // // 启动线程
        // my.run();
        // my.run();
        // 调用run()方法为什么是单线程的呢?
        // 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
        // 要想看到多线程的效果,就必须说说另一个方法:start()
        // 面试题:run()和start()的区别?
        // run():仅仅是封装被线程执行的代码,直接调用是普通方法
        // start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
        // MyThread my = new MyThread();
        // my.start();
        // // IllegalThreadStateException:非法的线程状态异常
        // // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
        // my.start();

        // 创建两个线程对象
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        my1.start();
        my2.start();
    }
}

程序的运行结果:

JavaSE之多线程(二)

 

从运行结果来看,我们并不能够分清楚哪个线程是哪个,只是大体的知道总共有两个线程在运行。那么如何获取各个线程的名称,以及如何设置线程的名称呢?

五、如何获取和设置线程对象的名称?

public class MyThread extends Thread {

    public MyThread() {
    }
    
    public MyThread(String name){
        super(name);//调用父类的带参构造
    }

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}

/*
 * 如何获取线程对象的名称呢?
 * public final String getName():获取线程的名称。
 * 如何设置线程对象的名称呢?
 * public final void setName(String name):设置线程的名称
 * 
 * 针对不是Thread类的子类中如何获取线程对象名称呢?
 * public static Thread currentThread():返回当前正在执行的线程对象
 * Thread.currentThread().getName() */
public class MyThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        //无参构造+setXxx()
        // MyThread my1 = new MyThread();
        // MyThread my2 = new MyThread();
        // //调用方法设置名称
        // my1.setName("林青霞");
        // my2.setName("刘意");
        // my1.start();
        // my2.start();
        
        //带参构造方法给线程起名字
        // MyThread my1 = new MyThread("林青霞");//此处若要使编译通过,必须在MyThread类中添加带参构造
        // MyThread my2 = new MyThread("刘意");
        // my1.start();
        // my2.start();
        
        //我要获取main方法所在的线程对象的名称,该怎么办呢?
        //遇到这种情况,Thread类提供了一个很好玩的方法:
        //public static Thread currentThread():返回当前正在执行的线程对象
        System.out.println(Thread.currentThread().getName());
    }
}

 六、线程调度及获取和设置线程优先级

之前讲过,假如计算机只有一个CPU的情况下,只能执行一件事儿,也就是说,只有当线程抢占到CPU的时间片,也就是CPU的使用权时才可以执行相应的指令,那么Java是如何对线程进行调度的呢?

  • 线程有两种调度模型 (了解即可) 

    分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。

    抢占式调度模型(Java):优先让优先级高的线程使用CPU,若优先级相同则随机选择一个,优先级高的线程得到的线程使用时间相对多些。

  • 如何获取和设置线程的优先级?

通过查看API,可以知道在Thread类中有三个关于优先级的常量字段,其中最高优先级为10默认优先级是5最低优先级是1

JavaSE之多线程(二)

public final int getPriority()://返回线程对象的优先级

public final void setPriority(int newPriority)//更改线程的优先级。 

若设置的线程优先级超过范围将抛出异常:IllegalArgumentException:非法参数异常。

当然,这里说的线程的优先级高,仅仅指的是获得CPU时间片的几率高些,所以只有在次数足够多的情况下,效果比较明显。

七、线程控制之休眠线程

import java.util.Date;

public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x + ",日期:" + new Date());//显示当前日期时间 // 睡眠
            // 困了,我稍微休息1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/*
 * 线程休眠
 *        public static void sleep(long millis)
* 
注意:调用该方法需要处理InterruptedException异常
*/ public class ThreadSleepDemo { public static void main(String[] args) { ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ThreadSleep ts3 = new ThreadSleep(); ts1.setName("林青霞"); ts2.setName("林志玲"); ts3.setName("林志颖"); ts1.start(); ts2.start(); ts3.start(); } }

 运行结果:

JavaSE之多线程(二)

八、线程控制之加入线程(强制运行)

加入线程的方法: public final void join():等待该线程终止。

在有多条线程情况下,强制让调用该方法的的线程先完全执行完毕,之后其他的线程才开始抢占CPU时间片,这有点类似于,古时候皇帝驾到,朝臣三跪九叩,直到皇帝上座并宣布礼毕时,朝臣们方可*活动。

九、线程控制之礼让线程

public class ThreadYield extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
            Thread.yield();
        }
    }
}

/*
 * public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 
 * 让多个线程的执行更和谐,但是不能靠它保证一人一次。
 */
public class ThreadYieldDemo {
    public static void main(String[] args) {
        ThreadYield ty1 = new ThreadYield();
        ThreadYield ty2 = new ThreadYield();

        ty1.setName("林青霞");
        ty2.setName("刘意");

        ty1.start();
        ty2.start();
    }
}

运行结果:
JavaSE之多线程(二)

 十、线程控制之守护线程

public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}


/*
 * public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
 * 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 
 * 
 * 游戏:坦克大战。
 */
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");

        // 设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

        Thread.currentThread().setName("刘备");
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}

 从上面这个例子中看,一共有三个线程(td1<关羽>,td2<张飞>,主线程<刘备>),从线程名称可以看出三个线程时存在关系的,明显关羽和张飞的存在是为了守护刘备,而在线程的执行代码中,刘备只打印4次,而关羽和张飞却要打印100次,在设置关羽和张飞为守护线程后,刘备打印结束那一刻,关羽和张飞相继结束自己。

十一、线程控制之中断线程

public final void stop():让线程停止,过时了,但是还可以使用。
 public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

这两种方法中前者已经过时,因为不太安全,若调用该方法的下面还有大量代码,则此时会都不执行;而后者在中断的时候抛出一个InterruptedException异常,较安全。