1. 多线程使用方法
使用多线程,绝大部分情况都是通过如下两种方式实现的,即继承Thread类或者实现Runnable接口。以下对两种方式分别进行介绍并比较。
1.1 使用Thread类实现多线程
自定义线程类要继承 Thread
类,并在 run
方法中实现自己的逻辑。参数的传递,可以采用构造方法传参或者设值的方式。
public class MyThread extends Thread
{
private String name;
public MyThread(String name)
{
this.name = name;
}
public void run()
{
for (int i = 0;i < 5;i++)
{
System.out.println("Thread name is " + name + ", say hello " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
Thread alice = new MyThread("Alice");
Thread bob = new MyThread("Bob");
alice.start();
bob.start();
}
}
1.2 使用Runnable接口实现多线程
自定义线程类要实现 Runnable
接口,重写 run
方法。参数的传递,可以采用构造方法传参或者设值的方式。
package com.example.demo.mutilThread;
public class MyRunnable implements Runnable
{
private String name;
public MyRunnable(String name)
{
this.name = name;
}
public void run()
{
for (int i = 0;i < 5;i++)
{
System.out.println("RunnableThread name is " + name + ", say hello " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
Thread cache = new Thread(new MyRunnable("Cache"));
Thread dodge = new Thread(new MyRunnable("Dodge"));
cache.start();
dodge.start();
}
}
1.3 小结
线程创建完毕之后,调用Thread的start方法,该并不是直接开始执行该线程,而是将该线程设置成为可运行的状态,即随时都都可以运行。至于什么时候真正的开始运行,是由操作系统的调度决定的。
在自定义线程类中重写的是run方法,但是在使用多线程的时候,调用的是start方法。打开start方法的实现,可以看到其中调用了本地方法start0,而在该方法中,最终调用了线程实例的run方法。如果在主线程中不使用start,而是使用run方法,那么则不会新开辟一个线程来执行其中的逻辑,而是直接在主线程中执行,跟普通的方法一样。
通过执行结果可以看到,多个线程之间的程序执行是乱序的,并不是每个线程按照某种固定的顺序执行。但是可以通过设计对应的控制逻辑,使得多个线程的执行变得有序。
对比两种方式,其实更加推荐使用Runnable的方式,因为实现Runnable接口,可以规避java中的单继承的限制,并且对于线程池来说,不能直接放入继承Thread的类。另外,Runnable的代码可以被多个线程共享(即用一个Runnable实现类初始化多个Thread实例),适合于多个线程处理同一资源的情况,比如买票场景。
2.线程状态
线程的各个状态以及之间的转换条件和路径如下图所示:
初始状态:Thread实例初始化完成。
可运行状态:调用线程实例的start方法后,线程进入到可运行线程池中等待运行。
运行状态:处于可运行状态的线程获取到了CPU,进入到运行状态,开始执行线程逻辑。
阻塞状态:阻塞状态是因为处于正在运行状态的线程放弃CPU。一般阻塞状态分为3种:
- 等待阻塞:线程执行了对象的wait方法。此时线程会释放持有的锁,被放置进入JVM的等待池中。
- 同步阻塞:线程在尝试获取同步锁失败,被放置进入锁池中。
- 其他阻塞:调用sleep方法或者join方法,或者等待IO的时候。
死亡状态:线程执行完毕或者异常退出run方法。
3 常用函数
-
Thread.sleep()
函数:该函数会使对应的线程休眠指定毫秒的时间。休眠结束之后会进入到可运行状态。执行休眠的时候不会释放锁。 -
Thread.yield()
函数:该函数会使当前线程让出CPU,从运行状态变成可运行状态。之后所有处于可运行状态的线程竞争CPU。高优先级的线程会优先被OS调度执行。如果是相同优先级,则平等竞争,但是存在一种情况是,让出CPU的线程被OS再次选中执行。需要注意的是,yield函数不会导致线程转到阻塞状态。 -
thread.join()
函数:该方法是让当前线程等待其他线程终止。比如在A线程中调用线程B.join(),那么执行的效果就是线程A从运行状态变成阻塞状态,一直等待线程B执行完毕,线程A才会转为可运行状态。join方法一般用于主线程中需要使用子线程的执行结果的情况。 -
thread.setPriority()
函数:设置线程的执行优先级。java的线程包含10个优先级,用1-10表示,数字越大,优先级越高。默认的优先级是5.
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
-
object.wait()
函数:该函数在synchronized代码块中使用,作用是使当前线程进入到等待状态,并释放锁。直到其他线程调用该对象的notify或者notifyAll方法进行唤醒,被唤醒之后,需要首先获取到对象锁,才能执行逻辑。 -
object.notify()
函数:唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程唤醒。 -
object.notifyAll()
函数:功能与notify类似,不同点在于notifyAll函数唤醒对象监视器上等待的所有线程。