4-5 《Java中多线程重点》——继承Thread实现Runnable死锁线程池Lambda表达式
Posted 美少女降临人世间
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4-5 《Java中多线程重点》——继承Thread实现Runnable死锁线程池Lambda表达式相关的知识,希望对你有一定的参考价值。
文章目录
多线程
多线程:栈空间独立,堆内存共享。
一、线程与进程
- 进程
正在运行的应用程序:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
每个进程都有着自己独立的堆、栈且是互不共享的
-
线程
- 进程中的一个执行路径(一个应用程序从执行到结束的整个过程),共享一个内存空间,一个进程中可以包含多条线程;
- 线程之间可以自由切换,并发执行;
- 一个进程最少有一个线程,线程控制着进程。
二、线程调度
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
-
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
三、同步与异步&并发与并行
同步:排队执行 , 效率低但是安全
异步:同时执行 , 效率高但是数据不安全
并发:指两个任务在同一时间段内都请求运行
处理器只能接收一个任务,把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行
- 如果用一台电脑先给友人A发个消息,然后立刻再给友人B发消息,然后再跟友人A聊,再跟友人B聊。这就叫并发。
并行:两个任务同一时刻运行,需要多核CPU
- 比如跟两个朋友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
四、多线程实现方式
1、继承Thread
步骤:
- 定义MyThread类并继承Thread类;
- 重写run方法,新的执行路径(通过thread对象的start()启动任务)
/**
* @author Elvira
* @date 2020/10/12 16:01
* @description 继承Thread
*/
public class MyThread extends Thread
@Override
public void run()
for (int i = 0; i < 10; i++)
System.out.println("嘿……"+i);
-
创建子类对象
-
调用start(),让线程执行
/**
* @author Elvira
* @date 2020/10/12 16:08
* @description 继承Thread的程序入口
*/
public class ThreadTest
public static void main(String[] args)
MyThread m = new MyThread();
m.start();
for (int i = 0; i < 10; i++)
System.out.println("嘻嘻……"
+i);
- 运行结果:可以看到顺序并不统一,这是因为线程在抢占时间片,谁先抢到就是谁的
线程时序图:
注意:运行过程中子线程任务中调用的方法都在子线程中运行
2、实现Runnable
步骤:
- 实现Runnable接口;
- 实现抽象方法run(),编写线程的任务;
/**
* @author Elvira
* @date 2020/10/12 16:22
* @description 实现Runnable
*/
public class MyRunnable implements Runnable
@Override
public void run()
for (int i = 0; i < 10; i++)
System.out.println("哼哼!" + i);
-
创建一个任务对象
-
创建一个线程,并给它一个任务
-
调用start()方法执行线程
/**
* @author Elvira
* @date 2020/10/12 16:30
* @description 实现Runnable的程序入口
*/
public class RunnableTest
public static void main(String[] args)
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 10; i++)
System.out.println("呵呵?" + i);
- 运行结果:
3、实现Runnable和继承Thread比较
继承Thread
- 优点:直接使用Thread类中的方法,代码简单
- 弊端:如果已有父类,不可用
实现Runnable接口(更常用)
-
其优点:
1. 通过创建任务,给线程分配任务实现多线程,更适合多个线程同时执行相同任务的情况
-
可以避免单继承带来的局限性
-
任务和线程分离,提高程序健壮性
-
(最重要)后续提供的线程池任务,接收Runnable类型任务,不接收Thread类型线程
-
-
弊端:不能直接使用Thread中的方法,需先获取线程对象,再得到Thread的方法,代码复杂
4、匿名内部类实现方式
- 继承Thread
/**
* @author Elvira
* @date 2020/10/12 16:52
* @description 继承thread的匿名内部类实现方式
*/
public class MyThread0
public static void main(String[] args)
new Thread()
public void run()
for (int i = 0; i < 10; i++)
System.out.println("一二三四五" + i);
.start();
for (int i = 0; i < 10; i++)
System.out.println("啦啦啦啦啦" + i);
- 运行结果:
- 实现Runnable
new Thread(new Runnable()
public void run()
for(int i = 0; i < 5; i++)
System.out.println("aaa" + i);
).start();
for (int i = 0; i < 5; i++)
System.out.println("bbb" + i);
- 运行结果:
五、Thread类
接下来列举API中Thread类最常用的方法:
构造器 | 描述 |
---|---|
Thread() | 分配新的 Thread 对象。 |
Thread(Runnable target) | 分配新的 Thread 对象。 |
Thread(Runnable target, String name) | 分配新的 Thread 对象。 |
Thread(String name) | 分配新的 Thread 对象。 |
常见方法:
变量和类型 | 方法 | 描述 |
---|---|---|
long | getId() | 返回此Thread的标识符。 |
String | getName() | 返回此线程的名称。 |
int | getPriority() | 返回此线程的优先级。 |
void | setPriority(int newPriority) | 更改此线程的优先级。 |
void | start() | 导致此线程开始执行; Java虚拟机调用此线程的run 方法。 |
static void | sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
static void | sleep(long millis, int nanos) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。 |
void | setDaemon(boolean on) | 将此线程标记为 daemon线程或用户线程。 |
特殊字段:控制线程抢到时间片的几率
变量和类型 | 字段 | 描述 |
---|---|---|
static int | MAX_PRIORITY | 线程可以拥有的最大优先级。 |
static int | MIN_PRIORITY | 线程可以拥有的最低优先级。 |
static int | NORM_PRIORITY | 分配给线程的默认优先级。 |
六、设置和获取线程名称
首先需要了解:currentThread() 可以获取当前正在执行的线程对象
- 获取线程名称
/**
* @author Elvira
* @date 2020/10/12 17:41
* @description 获取线程名称
*/
public class NameTest
public static void main(String[] args)
System.out.println(Thread.currentThread().getName());
//给线程指定一个名称
new Thread(new MyRunnable(), "Elvira").start();
实现类:
public class MyRunnable implements Runnable
@Override
public void run()
System.out.println(Thread.currentThread().getName());
- 运行结果:
如果不指定线程名称,直接给线程一个任务:再加两句
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
-
运行结果:
- 设置线程名称
方式一:上述,直接在创建线程任务时指定名称
//给线程指定一个名称
new Thread(new MyRunnable(), "Elvira").start();
方式二:setName()设置
Thread t = new Thread(new MyRunnable());
t.setName("THREAD");
t.start();
- 运行结果:
七、线程休眠sleep
sleep是Thread类的静态方法,类名直接调用即可。单位ms。
/**
* @author Elvira
* @date 2020/10/12 20:13
* @description 线程休眠3秒
*/
public class SleepTest
public static void main(String[] args) throws InterruptedException
for (int i = 0; i < 10; i++)
System.out.println(i);
Thread.sleep(3000);
-
运行结果:
每隔3秒蹦出来一个数字,最终打印了10个数字
线程阻塞:所有较耗时的操作都能称为阻塞。也叫耗时操作。
八、线程的中断
一个线程是一个独立的执行路径,它是否应该结束,由其自身决定。
因为线程执行过程会有很多资源需要使用或释放,如果干涉它的结束,很可能导致资源没能来得及释放,一直占用产生无法回收的内存垃圾。
早期有stop()方法可以结束线程,现在已经过时。现在出了新的方法,给线程打标记来控制它的结束。
标记是什么:interrupt
示例:
第一步:先设置main线程和Thread-0线程都执行循环数字1-10的任务
/**
* @author Elvira
* @date 2020/10/12 20:28
* @description 线程中断
*/
public class InterruptTest
public static void main(String[] args)
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 1; i <= 5; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
实现类:
public class MyRunnable implements Runnable
@Override
public void run()
for (int i = 1; i <= 10; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
- 运行结果:
第二步:给线程t1添加中断标记interrupt()
/**
* @author Elvira
* @date 2020/10/12 20:28
* @description
*/
public class InterruptTest
public static void main(String[] args)
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 1; i <= 5; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
t1.interrupt();
实现类:
public class MyRunnable implements Runnable
@Override
public void run()
for (int i = 1; i <= 10; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
//e.printStackTrace();
System.out.println("发现了中断标记,还没有停止。");
- 运行结果:
解释:发现了中断就会捕捉异常进入catch块,程序员决定该如何处理。不进行任何处理程序还会继续执行。此时在catch块中加入return,程序就会中断。
public class MyRunnable implements Runnable
@Override
public void run()
for (int i = 1; i <= 10; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
//e.printStackTrace();
System.out.println("发现了中断标记,在此处停止。");
return;
- 运行结果:线程结束(”自杀“)
九、守护线程
线程分为守护线程和用户线程
-
用户线程:当一个进程不包含任何存活的用户线程时,进行结束。
-
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡。
设置线程为守护线程:启动之前设置
t1.setDaemon(true);
完整代码:
/**
* @author Elvira
* @date 2020/10/13 9:32
* @description
*/
public class SetDaemonTest
public static void main(String[] args)
Thread t1 = new Thread(new MyRunnable());
t1.setDaemon(true);
t1.start();
for (int i = 0; i <= 5; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
实现类:
public class MyRunnable implements Runnable
@Override
public void run()
for (int i = 0; i <= 10; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
return;
- 运行结果:
十、线程安全问题
举一个生活中的小栗子,模拟买票窗口,创建三个线程,一起卖10张票。此时就会出现线程不安全的问题。
/**
* @author Elvira
* @date 2020/10/13 9:49
* @description
*/
public class ThreadSafeTest
public static void main(String[] args)
Runnable run = new Ticket();
//创建三个线程
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
买票任务:
/**
* 模拟买票窗口
*/
public class Ticket implements Runnable
private int count = 10;//票数
@Override
public void run()
while (count > 0)
//开始卖票
System.out.println(Java——10个关于Java中多线程并发的面试题