002-多线程的创建
多线程程序的引入:
如何实现多线程程序呢?
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
方式1:继承Thread类。
步骤
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()?
C:创建对象
D:启动线程
几个问题:
为什么要重写run()方法?
因为不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法
调用run()方法是单线程的效果,
是因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果。
start():首先启动了线程,然后再由 jvm去调用该线程的run()方法。产生的是多线程的效果。
为什么一个线程对象调用2次或2次异常会产生IllegalThreadStateException(非法的线程状态异常)?
MyThread t1 = new MyThread(); // 创建线程对象 t1 t1.start(); t1.start();
结果显示:会产生IllegalThreadStateException(非法的线程状态异常)
因为这个相当于是my线程被调用了两次。而不是两个线程启动。
代码:
MyThread.java
public class MyThread extends Thread { @Override public void run() { for(int i = 1; i <= 200; i++) { System.out.println("MyThread-----"+i); } } }
Test.java
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(); } }
线程的基本操作:
1. 如何获取线程对象的名称呢?
public final String getName():获取线程的名称。
2. 如何设置线程对象的名称呢?
public final void setName(String name):设置线程的名称
3. 针对不是Thread类的子类中如何获取线程对象名称呢?
public static Thread currentThread(): 返回当前正在执行的线程对象
Thread.currentThread().getName()
public class MyThread extends Thread { // 无参构造 public MyThread() { } // 有参构造 public MyThread(String name) { //super(name); 也可写成这种形式 setName(name); // 修改线程的名字 } @Override public void run() { for(int i = 1; i <= 200; i++) { System.out.println(getName()+"---------"+i); // 获取线程名 + 打印i的值 } } }
测试类
public class Test { public static void main(String[] args) { //1.无参构造的方式,构造线程对象 MyThread t1 = new MyThread(); // 创建线程对象 t1 MyThread t2 = new MyThread(); // 创建线程对象 t2 // 获取t1线程对象的名字 System.out.println( "t1默认线程名:"+t1.getName()); // Thread-0 // 获取t2线程对象的名字 System.out.println("t2默认线程名:"+ t2.getName()); // Thread-1 // 修改线程t1的名称 t1.setName("实况足球"); // 修改线程t2的名称 t2.setName("侠盗飞车"); t1.start(); // 启动线程t1 t2.start(); // 启动线程t2 System.out.println(); // 2.有参构造的方式构造线程对象的同时,修改线程名称 MyThread t3 = new MyThread("阿里巴巴"); MyThread t4 = new MyThread("央视影音"); t3.start(); // 启动线程t3 t4.start(); // 启动线程t4 // 3.获取main方法所在的线程对象的名称 Thread thread = Thread.currentThread(); // 返回当前正在执行的线程对象 System.out.println( "main方法所在的线程对象的名称: "+thread.getName()); // 打印线程名称 } }
分析打印结果:
产生问题: 默认线程名称格式为什么是:Thread-? 编号 如:Thread-0 Thread-1
查看Thread类的源码:
class Thread { private char name[]; public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //大部分代码被省略了 this.name = name.toCharArray(); } public final void setName(String name) { this.name = name.toCharArray(); } private static int threadInitNumber; //0,1,2 private static synchronized int nextThreadNum() { return threadInitNumber++; //return 0,1 } public final String getName() { return String.valueOf(name); } } class MyThread extends Thread { public MyThread() { super(); } }
public final void setDaemon(boolean on): 将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
游戏:坦克大战。
public class ThreadDaemon extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } }
public class ThreadDaemonTest { public static void main(String[] args) { ThreadDaemon td1 = new ThreadDaemon(); ThreadDaemon td2 = new ThreadDaemon(); td1.setName("关羽"); td2.setName("张飞"); td1.setDaemon(true); // 将td1线程标记为守护线程 td2.setDaemon(true); // 将td2线程标记为守护线程 td1.start(); // 启动线程td1 td2.start(); // 启动线程td2 Thread.currentThread().setName("刘备"); // 设置当前主线程名字为"刘备" for (int x = 0; x < 5; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } } }
分析打印结果
public final void join() : 等待该 线程终止。
public class ThreadJoin extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } }
package com.itcast_04; public class ThreadJoinTest { public static void main(String[] args) { // 创建3个线程 ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj1.setName("朱元璋"); tj2.setName("朱允炆"); tj3.setName("朱 棣"); tj1.start(); // 开启线程tj1 try { tj1.join(); // 等待tj1线程终止 } catch (InterruptedException e) { e.printStackTrace(); } tj2.start(); // 开启线程tj2 tj3.start(); // 开启线程tj3 } }
分析结果:
public final int getPriority():返回线程对象的优先级
public final void setPriority(int newPriority):更改线程的优先级。
注意:
线程默认优先级是5。
线程优先级的范围是:1-10。
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
IllegalArgumentException: 非法参数异常。
-抛出的异常表明向方法传递了一个不合法或不正确的参数。
public class ThreadPriority extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); } } }
public class ThreadPriorityTest { public static void main(String[] args) { // 创建3个线程对象 ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); ThreadPriority tp3 = new ThreadPriority(); // 更改线程名称 tp1.setName("乔 峰"); tp2.setName("虚 竹"); tp3.setName("段 誉"); // 获取默认优先级 System.out.println("打印tp1默认优先级:"+tp1.getPriority()); System.out.println("打印tp2默认优先级:"+tp2.getPriority()); System.out.println("打印tp3默认优先级:"+tp3.getPriority()); System.out.println(); // 设置线程优先级 // tp1.setPriority(100000); // 报错原因:超出1-10这个范围 //设置正确的线程优先级 [1-10] tp1.setPriority(10); // 最大值 tp2.setPriority(1); // 最小值 tp1.start(); tp2.start(); tp3.start(); } }
public static void sleep(long millis) 线程休眠
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()); try { Thread.sleep(2000); // 休眠2秒钟 } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class ThreadSleepTest{ public static void main(String[] args) { // 创建三个线程对象 ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ThreadSleep ts3 = new ThreadSleep(); // 设置线程名称 ts1.setName("许一乐"); ts2.setName("许二和"); ts3.setName("许三多"); // 开启3个线程 ts1.start(); ts2.start(); ts3.start(); } }
public final void stop( ) :让线程停止, 过时了,但是还可以使用。
public void interrupt( ) : 中断线程。 把线程的状态终止,并抛出一个InterruptedException。
package com.itcast_04; import java.util.Date; public class ThreadStop extends Thread { @Override public void run() { System.out.println("开始执行:" + new Date()); // 我要休息10秒钟,亲,不要打扰我哦 try { Thread.sleep(10000); } catch (InterruptedException e) { // e.printStackTrace(); System.out.println("线程被终止了"); } System.out.println("结束执行:" + new Date()); } }
public class ThreadStopTest { public static void main(String[] args) { // 创建线程对象 ThreadStop ts = new ThreadStop(); // 开启线程 ts.start(); try { // 你超过三秒不醒过来,我就干死你 Thread.sleep(3000); // 主线程休眠3秒 // ts.stop(); // 过时方法 缺点:过于暴力 ts.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void yield( ) :暂停当前正在执行的线程对象,并执行其他线程。 (礼让)
让多个线程的执行更和谐,但是不能靠它保证一人一次。
public class ThreadYield extends Thread { @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println(getName() + ":" + x); Thread.yield(); // 线程的礼让 } } }
public class ThreadYieldTest { public static void main(String[] args) { ThreadYield ty1 = new ThreadYield(); ThreadYield ty2 = new ThreadYield(); ty1.setName("詹姆斯"); ty2.setName("小 毛"); ty1.start(); ty2.start(); } }
分析结果:
有缺陷,可以使用 唤醒机制来解决
方式2:实现Runnable接口
步骤:
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class MyRunnableTest {
public static void main(String[] args) {
// 创建MyRunnable类的对象 (Runnable接口的实现)
MyRunnable my = new MyRunnable();
// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
// 构造方式一:Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("詹姆斯");
// t2.setName("小 毛");
// 构造方式二: Thread(Runnable target, String name)
// 创建两个线程,并修改线程名字
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
// 开启线程
t1.start();
t2.start();
}
}
2种创建多线程方式的比较: