这篇文章继续上篇的内容。
四、如何实现多线程程序?
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。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(); } }
程序的运行结果:
从运行结果来看,我们并不能够分清楚哪个线程是哪个,只是大体的知道总共有两个线程在运行。那么如何获取各个线程的名称,以及如何设置线程的名称呢?
五、如何获取和设置线程对象的名称?
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。
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(); } }
运行结果:
八、线程控制之加入线程(强制运行)
加入线程的方法: 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(); } }
运行结果:
十、线程控制之守护线程
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异常,较安全。