一个进程正在运行时至少会有一个线程在运行。每个线程都是一个独立的执行流,多个线程之间是“并发执行的”。
我们看第一个多线程程序:
public class Main { public static void main(String[] args) { while(true){ System.out.println(Thread.currentThread().getName()); } } }
运行结果:
这里打印出的main其实就是一个名称为main的线程在执行main()方法中的代码。
下面我们可以使用jconsole命令来观察该线程,使用方法如下:
1,在控制台点击Terminal窗口直接输入jconsole命令
2,选择本地进程,选择并连接
3,查看要观测的线程
创建线程
1. 继承Thread类
java的JDK开发包自带了对多线程技术的支持,通过它可以便捷的进行多线程编程,继承Thread类是其中的一种重要方式,另一种则是实现Runnable接口。
我们首先创建一个自定义的线程类MyThread,此类应该继承Thread,并且重写其中的run方法,在run()方法中添加线程要执行的任务代码如下:
public class Main {
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程运行!");
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.println("运行结束!");
}
}
运行结果为:
在上述代码中使用start()方法来启动一个线程,在线程启动后会自动调用线程对象中的run()方法,他就是线程执行任务的入口。而就运行结果来看MyThread类中的run()方法执行时间要比输出“运行结束”的时间晚,这是因为start()方法在被调用时执行了多个步骤,其中还需要操作系统开辟内存而main线程在执行start()方法时不必等待这些步骤都执行完毕,而是立即执行start()方法之后的代码,由于输出“运行结束!”耗时比较少,所以在大多数情况下先输出“运行结束!”。
2. 实现Runnable接口
1.自定义MyRunable类实现Runable接口;
2.创建Thread类实例,调用Thread的构造方法时将MyRunnable对象作为target参数;
3.调用start()方法;
代码如下:
public class Main {
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("线程运行!");
}
}
public static void main(String[] args){
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
3.对比这两中创建线程的方式:
1,继承Thread类,直接使用this就表示当前线程对像的引用;
2,实现Runnable接口,this表示的是MyRunnable的引用,要想获得当前线程对象的引用需要使用Thread.currentThread()方法;
3,由于Java的单继承模式,使用继承Thread类的方式来创建线程是有局限性的,为了打破这种限制就可以使用实现Runable接口的方式来实现多线程技术。
4.其他变形
4.1 匿名内部类创建Thread子类对象
代码:
public class Main {
public static void main(String[] args){
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("线程运行!");
}
};
t1.start();
}
}
运行结果:
4.2 匿名内部类创建Runnable子类对象
代码:
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable!");
}
});
4.3 lambda表达式创建Runnable子类对象
代码:
Thread t3 = new Thread(() -> System.out.println("lambda"));
Thread类及常见方法
每个执行流也需要有一个对象来描述,而Thread类对象就是用来描述一个线程的执行流的,jvm会将这些Thread对象组织起来,用于线程调度,线程管理。每一个线程都有一个唯一的Thread对象与之关联。
1.Thread的常见构造方法
使用Runnable对象作为target参数来创建线程我们已在上文中做了演示这里我们不再赘述,而String name参数就是为创建的线程命名。
2.Thread的几个常见属性
1.ID是线程的唯一表示,不同的线程不会重复;
2.Name会在各种调试工具中被用到;
3.优先级高的线程理论上来说更容易被调度到;
4.状态表示当前线程所处的情况,我们在后文中详细解读;
5~6.线程的中断问题,我们在后文中举例说明;
7.是否存活指的是系统中的线程是否被销毁,可以简单的理解为run方法是否运行结束;
8.是否为守护线程即后台线程,jvm会在一个线程的所有非后台线程结束后才结束运行。
看下面这段代码和其运行结果:
public class Main {
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
Thread thread = new Thread(new MyRunnable(),"线程1");
thread.start();
while(thread.isAlive()){
System.out.println("线程ID:" + Thread.currentThread().getId() + "线程名字:" + Thread.currentThread().getName()
+ "状态:" + Thread.currentThread().getState());
}
}
}
2.启动一个线程的方法start()
当我们调用start方法时,程序首先通过jvm告诉操作系统创建Thread,操作系统再开辟内存并创建Thread线程对象,随后操作系统对Thread对象进行调度,以确定执行时机,最后Thread在操作系统中被成功执行,既让线程执行具体的任务,具有随机顺序执行的效果。如果调用run方法,直接由main主线程来调用run方法,也就是必须等run方法中的代码执行完毕后才可以执行后面的代码。
3.中断一个线程
1.自定义变量作为标志位(加上volatile关键字)
代码如下:
public class Main {
static class MyRunnable implements Runnable{
public volatile boolean isQuit = false;
@Override
public void run() {
while(!isQuit){
System.out.println("线程运行中");
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
Thread.sleep(2000);
myRunnable.isQuit = true;
System.out.println("线程停止");
}
}
运行结果:
2.使用Thread对象的interrupt()方法通知线程结束
①isInterrupted是用来判断对象关联的线程标志位是否被设置,调用后不清楚标志位
代码:
public class Main {
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//System.out.println(Thread.interrupted() + "" + i);
System.out.println(Thread.currentThread().isInterrupted() + "" + i);
}
}
}
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
thread.interrupt();
}
}
运行结果为:
该线程被中断之后相当于按下了弹不起来的开关这种情况被称为‘‘不清除标志位”
②interrupted是用来判断当前线程的中断标志位是否被设置,是一个静态方法调用后清楚标志位,相当于按下开关后,开关又自动弹起来了;
示例代码:
public class Main {
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted() + "" + i);
// System.out.println(Thread.currentThread().isInterrupted() + "" + i);
}
}
}
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
thread.interrupt();
}
}
运行结果:
4.等待一个线程join()
join()的作用是等待线程对象的销毁,先看下面这段代码:
public class Main {
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print( i + " ");
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread1.join();
System.out.println("thread1结束工作");
thread2.start();
thread2.join();
System.out.println("thread2结束工作");
}
}
运行结果为:
以这段代码来说,thread1.join()的作用是使所属的线程对象thread1正常执行run()方法中的任务,而使当前线程无限期阻塞,等待thread1线程销毁后再继续执行当前线程之后的代码,具有串联执行的效果,thread2.join()方法的作用也是如此。
第二个重载的方法如图所示,它的涵义就是等待线程结束,但是最多等millis毫秒。
需要特别注意的是:在使用join()方法的过程中,如果当前线程对象被中断,则当前线程出现异常并且与先后顺序无关。