文章目录
多线程编程
一、创建多线程
方法一:继承Thread类
public class ThreadDemo1 { public static void main(String[] args) { MyThread t = new MyThread(); t.start();//开启一个新线程,同时执行t线程中的run(),线程与线程之间互不影响 } } class MyThread extends Thread{ @Override public void run() { System.out.println("线程开始执行"); } }
-
匿名内部类创建 Thread 子类对象
Thread t = new Thread(){ @Override public void run() { System.out.println("线程开始执行"); } };
方法二:实现Runnable接口
public class ThreadDemo3 { public static void main(String[] args) { Thread t = new Thread(new MyThread3()); t.start(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println("MyThread线程"); } }
-
匿名内部类创建 Runnable 子类对象
Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("MyThread线程"); } });
-
lambda 表达式创建 Runnable 子类对象
Thread t = new Thread(() -> { System.out.println("Thread线程"); } });
二、 Thread类及常见方法
2.1 Thread 的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(“线程名字”);
Thread t4 = new Thread(new MyRunnable(), “线程名字”);
一个线程我们怎么能看到他的名称?设置名称有什么用?
public class ThreadDemo { public static void main(String[] args) { Thread t = new Thread(()->{ while(true){ System.out.println("Thread线程"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); while(true){ System.out.println("main线程"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
- 运行ThreadDemo程序
- 在我们本地JDK路径下找到jconsole.exe文件,双击打开
C:\Program Files\Java\jdk1.8.0_291\bin\jconsole.exe
- 这里我们可以看到本地运行的程序,选中点击链接
-
选择线程,下面我们就可以观察到ThreadDemo中的所有正在运行的线程。其中Thread-0就是我们自己创建的线程,这是没有设置线程名称的时候。
修改代码:
Thread t = new Thread(()->{ while(true){ System.out.println("Thread线程"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },"我的线程");
作用:利用jconsole方便我们观察线程运行情况,同时设置线程名有利于我们区分线程。
2.2 Thread 的常见属性
属性 | 方法 | 说明 |
---|---|---|
ID | getId() | ID 是线程的唯一标识,不同线程不会重复 |
名称 | getName() | 名称是各种调试工具用到 |
状态 | getState() | 状态表示线程当前所处的一个情况 |
优先级 | getPriority() | 优先级高的线程理论上来说更容易被调度到 |
是否后台线程 | isDaemon() | JVM会在一个进程的所有非后台线程结束后,才会结束运行 |
是否存活 | isAlive() | run 方法是否运行结束了 |
是否中断 | isInterrupted() | 是否停止运行 |
- Thread的构造方法可以设置线程名称,我们也可以只有setName()来设置线程名称。
- 线程的优先级有如下特点:
- Java 线程的优先级从 1~10;
- Java 线程默认优先级是 5;
- Java 线程的优先级继承于创建它的线程。
- 前台线程和后台线程的区别
- isDaemon()为true:后台线程,不会阻止java进程的结束,即使后台线程没有执行完。
- false:前台线程,会阻止java进程的结束,只有在所有的前台进程都执行完了之后,java进程还能结束。
2.3 启动一个线程-start()
public class ThreadDemo { public static void main(String[] args) { Thread t = new Thread(()->{ while(true){ System.out.println("Thread线程"); } }); t.start(); while(true){ System.out.println("main线程"); } } }
t.start() :不论是继承Thread还是实现Runnable接口创建的线程,只能算是把线程创建出来,并没有真正开始执行,只有在调用 start 方法后, 才真的在操作系统的底层创建出一个线程。
线程是独立的执行流
t线程和main线程是独立执行的,互不影响,这也是为什么控制台会交替输出内容的原因。
t.start()与t.run()的区别:t.run()也可以执行线程,但是它是在当前线程的基础上去执行的,只有t线程执行结束,才能继续执行当前线程,它影响其他线程的执行,导致程序无法并发执行。
观察代码,分析结果
public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("1"); } }); t.start(); System.out.println("2"); }
从控制台输出内容可以看到 2 大多数会比 1 先输出,但是不排除 1 比 2 先输出的情况。
原因:线程的创建也有开销
2.4 休眠当前线程-sleep()
**作用:**它可以让当前线程暂停一段时间,之后再正常运行。
**单位:**毫秒
因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间。
public class ThreadDemo { public static void main(String[] args) { Thread t = new Thread(()->{ while(true){ System.out.println("Thread线程");//每隔一秒输出一次结果 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } }
2.5 等待一个线程-join()
由于线程的执行时随机的,有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。
public class ThreadDemo7 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ for (int i = 0; i < 5; i++) { System.out.println("t线程"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); //1.无参数:一直等待 //2.有参数:达到一定时间,就不等到了,两个线程同时执行 t.join(); // t.join(3000);//main线程等待t线程3秒 System.out.println("main线程"); } }
- 无参数
使用了t.join之后,main线程只有在t线程执行结束后才能执行。
如果t线程一直不结束,那么main就变成阻塞等待状态,一直等待t线程执行结束
- 有参数
如果设置的等待时间,就不再继续等待。
2.6 获取当前线程引用
返回当前线程的对象
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
2.7 中断一个线程
方法一:用一个变量来控制线程的执行
public static boolean flag = false; public static void main(String[] args) { //此时的flag为局部变量,lambda表达式要满足变量捕获(变量被final修饰或者为实际final,也就是不会被修改) // boolean flag = false; Thread t = new Thread(()->{ while(!flag){ System.out.println("Thread线程"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread线程中止"); }); t.start(); try { Thread.sleep(3000);//主线程休眠3秒,也就是让分支线程执行3秒,再停止 } catch (InterruptedException e) { e.printStackTrace(); } flag=true; }
方法二:使用Thread内置标志位
public static void main(String[] args) { Thread t = new Thread(()->{ //Thread.currentThread()获取当前线程t //isInterrupted内置标识符,默认是false while(!Thread.currentThread().isInterrupted()){ System.out.println("Thread线程"); try { //被唤醒sleep后,报异常并检查interrupt,把true变成false Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //报异常后线程不会停止,加上break才会结束。 //break; } } }); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //设置成true,表示中断 //会唤醒t线程的sleep(),再从设置成false //多个t.interrupt();只会报一次异常 t.interrupt(); }
interrupt方法的作用:
- 设置标志位为true
- 如果线程正处于阻塞状态(执行sleep()),它会把阻塞状态唤醒,通过跑异常的方式让sleep结束。
上述代码并不会在报异常以后停止运行:
当sleep被唤醒后,它会自动清除标志位(true => false)
以至于下次循环能够正常执行
解决办法:在报异常的同时执行break
三、线程的状态
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了