Java中多线程是一个大的知识点,也是最重要的知识点。
你听到的秒杀抢购、多并发、负债均衡等关键词都跟它有关系。
本篇目录:
1、什么是多线程 2、创建线程 3、线程生命周期 4、线程管理 5、线程同步
1 什么是多线程
举个例子
包饺子需要饺子皮和饺子馅两样材料。
如果你一个人包饺子,就需要先擀饺子皮后剁饺子馅,又或者反之。总之,你不能同时干这两件事情,这就叫单线程工作。
又如果你的朋友跟你一起包饺子,你擀饺子皮,他剁饺子馅,最后一起包饺子。你们两个同步进行,效率很明显比你一个人要高很多,而这就是多线程。
2 创建线程
Java中提供了三种方式创建线程,在没有特殊需求的情况下我推荐使用Lambda表达式创建线程,简单方便、代码也更简洁。
Thread t1=new Thread(()->{
System.out.println("创建线程");
});
t1.start();// 启动线程Runnable runnable=()->{
System.out.println("创建线程");
};
runnable.run();// 启动线程Callable接口创建线程与上面两种方式有点不同。Callable有返回值,并且可以抛出异常。
这让Callable不同于其他两种方式,我们可以通过返回值来获取线程的工作状态,在很多场景中是非常重要的。
如果你只是临时创建几个线程,建议你这里还使用Lambda表达式。
try {
Callable callable=()->{
System.out.println("创建线程");
return "call方法返回值";
};
callable.call();
} catch (Exception exception) {
exception.printStackTrace();
}如果你有其他的业务需求,建议你搭配FutureTask使用。
FutureTask futureTask=new FutureTask(new Callable() {
@Override
public String call() throws Exception {
System.out.println("创建线程");
return "call方法返回值";
}
});
futureTask.run();3 线程生命周期
线程的生命周期有五个状态:新建、就绪、运行、阻塞、销毁。
新建:该线程实例刚被new出来,还未调用运行方法。
就绪:调用运行方法后,线程即处于等待CPU分配资源阶段,谁先拿到CPU资源谁就开始运行。
运行:当线程拿到CPU资源后即进入运行状态,run方法定义了线程执行的操作。
阻塞:当线程处于运行中时,调用了sleep、wait方法后进入阻塞状态并释放CPU资源,这时候需要其他线程将其唤醒、或等待睡眠期满后重新进入就绪状态。
销毁:如果线程正常执行完毕或被提前强制终止,又或出现异常都会销毁并释放CPU资源。
4 线程管理
下面是Java为我们提供的一些对线程管理方法。
// 使该线程睡眠1000毫秒
Thread.sleep(1000);sleep方法是Thread类的静态方法。需要注意的是,调用该方法睡眠的是当前运行该代码的线程,如果你在Main线程中执行其他线程的实例调用该方法,睡眠的还是主线程,而非线程实例。
// 让出当前CPU的资源,重新进入就绪状态
Thread.yield();与sleep方法类似,yield方法是Thread类的静态方法。
与sleep方法不同的是yield方法不会使当前线程进入阻塞状态,而是进入就绪状态。它的作用只是让当前线程暂停一下,将当前的CPU资源让给它资源,自己再去等待CPU的资源。
不推荐使用yield方法。
Thread t1=new Thread(()->{
demoA("线程1");
});
t1.setPriority(5);// 设置线程优先级
t1.start();可以通过Thread类提供的setPriority方法来设置线程的优先级,优先级高的线程会获得更多的执行机会。
setPriority方法有一个int类型参数,该参数值应该在1-10之间的整数。
需要注意的是该方法非静态,需要通过实例对象调用,或在继承了Thread类后在非静态方法中调用父类方法。
当该线程在运行中我们需要它停止运行时,可以使用一个变量来判断该线程是否要继续运行。
如:
Thread t1=new Thread(()->{
while (true) {
// 判断当前线程是否还有运行的条件
if(number<1){
break;
}
demoA("线程1");
}
});设置如上的变量,可在任何时候任何地方设置该线程的运行条件,让线程正常的执行完run方法内容。
5 线程同步
当多个线程共享一个资源时,就会有线程不同的情况发生。
比如买奶茶,每杯奶茶都有自己的编号,如果多人同时去买奶茶,但是线程没有做同步,就会买到同一杯奶茶。

再比如买火车票、买电影票,多个人买到同一张票,这个位置应该谁来坐就成了问题。
final Object i=1;// 锁
public void demoA(String name){
synchronized (i){
if(number!=0){
number--;
System.out.println(name+"买到奶茶"+number);
}
}
}使用synchronized关键字引入锁,保证线程之间共享同一锁,只有抢到锁的线程才可以执行该代码块。
同时synchronized关键字还可以加到方法上,实现的功能相同,如:
public synchronized void demoA(String name){
if(number!=0){
number--;
System.out.println(name+"买到奶茶"+number);
}
}volatile关键字的作用是告诉虚拟机该域可能被其他线程更新,所以每次使用该域都要重新计算,而不是使用寄存器中的值。
但是volatile关键字只能保证线程在读取该值时为最新的,无法保证该值的原子性,这就很有可能依然会出现两个人买到同一张电影票的可能。