目录
1.进程
2.线程
3.进程与线程的区别与联系
4.为什么会有线程?
5.创建第一个多线程程序
方法一:继承Theard类
方法二:实现Runnable接口
方法三:匿名内部类创建Thread子类对象
方法四:匿名内部类创建 Runnable 子类对象
方法五(推荐方法):lambda 表达式创建 Runnable 子类对象
体会多线程
6.Thread类常见的构造方法及其属性
6.1Thread常见构造方法
6.2Thread常见属性
6.3线程的启动方法
6.4线程的休眠方法
6.5线程的中断方法
当interrup方法遇上线程阻塞
6.6线程的等待方法
6.7当前执行中线程的获取方法
7.线程的状态
1.进程
要了解多线程,首先要明白进程的概念,简单的来说;
一个已经跑起来的程序就可以称为进程.而使用进程,就是为了能够并发的运行多个任务.
2.线程
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码
3.进程与线程的区别与联系
- 进程是系统分配资源的最小单位,而线程是系统调度的最小单位.
- 进程至少要包含一个线程,这个线程称为主线程.
- 一个进程中可以有着多个线程.
- 线程对于进程来说,在创建,销毁和调度方面速度更快,更轻量.
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间
- 多个程序(即进程)之间是互不影响的,像是QQ与微信.如果你QQ发生了卡顿或者出现了BUG一类事情,并不会影响到微信的工作.
- 在同一个进程的线程之间,会相互的影响.
4.为什么会有线程?
为了提高程序的运行效率和响应的速度,就有了并发编程的出现.
对于并发编程来说,简单的讲就是可以讲一个任务,分成多个小的子任务去并发的执行.
当然,进程也可以实现并发编程,但线程更轻量的同时也能达到这一目的.所以通常我们使用多线程,即在一个进程中创建多个线程来实现并发编程.
5.创建第一个多线程程序
其实创建一个线程的本质是,创建一个Thread(在Thread类中实现了Runnable接口)实例,在需要启动线程的时候调用实例的start方法,start方法再去调用到Runnable接口中的run方法.
我们在run方法中写入我们想要线程执行的程序,在启动的线程的时候,线程就会相应的执行run方法中的代码.
方法一:继承Theard类
创建一个类MyThread继承父类Thread,并作为其子类.
再重写其父类当中的run方法
class MyThread extends Thread{//创建一个继承Thread的类
@Override
public void run() {//并重写其的run方法,在run方法中就是线程要执行的编程
while(true){
System.out.println("我是一个小工具人");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();//通过创建好的类,创建一个实例
thread.start();//调用实例的start方法,来启动一个线程来执行run中的编程
while(true){
System.out.println("我是主线程");
}
}
}
方法二:实现Runnable接口
创建一个MyRunnable类并实现Runnbale接口
在创建Thread实例的时候,将实例化后的MyRunnable传入Thread构造方法当中,之后该线程就使用MyRunnable类的实例作为运行任务
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是一个小工具人");
}
}
}
public class Test1 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();//线程开始运行
while(true){
System.out.println("我是主线程");
}
}
}
方法三:匿名内部类创建Thread子类对象
在创建Thread实例的时候,写一个匿名内部类,这个内部类作为Thread的子类.和方法一类似
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
while(true){
System.out.println("我是一个小工具人");
}
}
};
thread.start();
while(true){
System.out.println("我是主线程");
}
}
方法四:匿名内部类创建 Runnable 子类对象
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("我是一个小工具人");
}
}
});
thread.start();
while(true){
System.out.println("我是主线程");
}
}
方法五(推荐方法):lambda 表达式创建 Runnable 子类对象
我们线程的本质是Runnable接口中的run方法,Runnable接口本身也是一个函数式接口(一个接口有且只有一个抽象方法即run)
而同时,Thread类又实现了Runnable接口,所以我们可以对Thread使用lambda表达式来以更简洁的方式重写run方法
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true){
System.out.println("我是一个小工具人");
}
});
thread.start();
while(true){
System.out.println("我是主线程");
}
}
体会多线程
class MyThread extends Thread{
@Override
public void run() {//我们创造出的线程thread执行的
for(int i = 0; i < 10; i++){
try {
Thread.sleep(1000);//每次打印都休眠1s,因为代码执行的速度太快.
//线程调度的切换时间要长一点,
//不休眠在打印台上就体现不出多线程的效果(视觉上)
//别着急,后文有提到sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wow");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();//线程创建的方法随意哈
thread.start();//创建启动线程thread
for(int i = 0; i < 10; i++){//主线程main要执行的
Thread.sleep(1000);
System.out.println("TT");
}
}
}
可以看到,两个线程是并行着执行打印语句的
6.Thread类常见的构造方法及其属性
6.1Thread常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread thread1 = new Thread();
//参照方法一
Thread thread2 = new Thread(new MyRunnable());
//参照方法二
Thread thread3 = new Thread("ThreadName");
//创建一个实例,并赋予线程名字
Thread thread4 = new Thread(new MyRunnable(),"ThreadName2");
//使用MyRunnable对象创建线程,并赋予线程名字
6.2Thread常见属性
属性 | 获取方法 | 说明 |
ID | getId() | 获取线程的唯一标识,就像每个人的身份证号,不会重复 |
名称 | getName() | 获取线程名 |
状态 | getState() | 获取线程目前的状态(线程状态将在后文提及) |
优先级 | getPriority() | 获取线程的优先级,优先级越高越容易被调用 |
是否后台线程 | isDaemon() | 查看线程是否为后台线程,在jvm中需要非后台线程全部结束后才结束 |
是否存活 | isAlive() | 查看线程是否执行完成run方法中的代码 |
是否被中断 | isInterrupted() | 查看线程是否被中断(关于线程中断将在后文提及 |
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for(int i = 0; i < 5; i++){
System.out.println("wow!!!");
}
System.out.println("OH??!");
});
System.out.println(Thread.currentThread().getName() + "-ID: " + Thread.currentThread().getId());
//获取当前执行的线程名 + 其线程ID
System.out.println(Thread.currentThread().getName() + "-NAME " + Thread.currentThread().getName());
//线程名 + 其线程名字
System.out.println(Thread.currentThread().getName() + "-STATE: " + Thread.currentThread().getState());
//线程名 + 其线程状态
System.out.println(Thread.currentThread().getName() + "-PRIORITY: " + Thread.currentThread().getPriority());
//线程名 + 其线程优先级
System.out.println(Thread.currentThread().getName() + "-IsDAEMON: " + Thread.currentThread().isDaemon());
//线程名 + 其是否为后台线程
System.out.println(Thread.currentThread().getName() + "-IsALIVE: " + Thread.currentThread().isAlive());
//线程名 + 其是否还在运行
System.out.println(Thread.currentThread().getName() + "-IsINTERRUPTER: " + Thread.currentThread().isInterrupted());
//线程名 + 其是否被中断
thread.start();
//开启我们创建的线程,没错上面的语句的线程都为主线程哈哈哈
}
- 关于后台线程与前台线程,我们创建的线程默认为前台线程.也可以使用setDaemon方法将其更改.
- 在前台线程没有执行完毕之前,java进程是不会关闭的.即java进程要关闭的时候,如果只有后台线程在运行,则会直接关闭,如果前台线程还在运行则会等待前台线程执行完毕之后才会关闭.
6.3线程的启动方法
方法 | 说明 |
public void start() | 创建并启动一个线程 |
当我们在想创建一个线程的时候,第一是覆写run方法创建一个Thread的实例.
但是创建Thread的实例并不相当于创建了一个线程,就像是你拿到了一个工具人的电话.
而run方法里的就是这位工具人(线程)接下来要干活的清单.
state方法则是联系工具人(线程)出现,并去执行. 只有调用了state,工具人才会出现,也是这个时候才是真正意义上在底层操作系统上创建出一个线程
6.4线程的休眠方法
这里的休眠是指,线程临时暂停执行,待我们规定的休眠时间一过便会继续执行后面的程序
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for(int i = 0; i < 10; i++){
try {
Thread.sleep(1000);//休眠的时间为1000ms == 1s
System.out.println("working~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
6.5线程的中断方法
一旦真正调用state方法创建并启动线程,除非完成run中的代码.否则线程是不会中途停止的.
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
- 在Thread类内部有一个boolean变量作为线程是否被中断标记.
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
在创建线程thread的三秒后,调用interrupt方法设置其标志位,通知线程中断
在thread线程内使用Thread.interrupted()或Thread.currentThread().isInterrupted()来观察中断标志位,
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!Thread.interrupted()){//thread线程使用interrupted来检测是否要进行中断
System.out.println("working~");
}
});
thread.start();//thread线程启动
System.out.println("gogogo");
Thread.sleep(1000 * 3);//main线程休眠三秒,在main休眠的同时,thread在不断工作
thread.interrupt();//三秒后thread线程被通知进行中断
}
当interrup方法遇上线程阻塞
我们来仔细的分析interrupt方法的说明,
中断对象关联的线程,设置其中断标志位.如果线程为阻塞状态,则将其中断标志位清除
在调用interrupt方法通知线程要进行中断了,设置其中断标志位为true后(仅针对下列代码举例说明):
- 如果线程为阻塞状态,则是将线程唤醒,将其线程状态调整为不阻塞状态.清除中断标志位(设置为false)再进入到catch语句当中抛出异常.中断标志位被清除后,thread线程的interrupted方法接收到的中断标志位为false(没有检测到要进行中断)则是会继续执行while语句无限循环.
- 如果线程为不阻塞状态,则是由interrupted方法检测到中断标志位的改变,中断thread线程的执行.
- 阻塞:像是使用wait方法,sleep方法和join方法等其他的来导致线程不去执行下面的代码,或等到一个特定的条件后再去执行
Thread thread = new Thread(() -> {
while(!Thread.interrupted()){
System.out.println("working~");
//结果发现线程为阻塞状态,则唤醒线程(将线程更改为不阻塞)来到catch语句下抛出异常
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//来到这个语句下,抛出异常
//但其中断标志位,是没有设置的.所以说在抛出异常后线程没有进行中断
//会继续进行while循环语句
//但我们可以在catch语句最后加上一个break;使其跳出这个while
e.printStackTrace();
//break;
}
}
});
thread.start();
System.out.println("gogogo");
Thread.sleep(1000 * 3);//main线程进入sleep休眠(阻塞)3秒
thread.interrupt();//通知thread线程要中断
上面的代码结果如下,在进入到catch语句抛出异常.但thread线程的while还在继续,此时main线程已经执行完了,也不会再次调用中断方法.此时的while循环中的表达式可以看成为true了
当我们在catch语句上加上一个break;可以看到thread在抛出异常后再执行break就可以跳出循环实现我们想要的中断功能.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
6.6线程的等待方法
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
使用实例:
public static void main(String[] args) throws InterruptedException {
//创建线程实例0
Thread thread0 = new Thread(() -> {
for(int i = 0; i < 4; i++){
System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":唱完了");
});
//创建线程实例1
Thread thread1 = new Thread(() ->{
try {//在thread1中调用thread0的join.作用为:等待thread0执行完后才执行thread1线程.thread1当前为阻塞状态
thread0.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {//因为在进程中线程是随机调度的,
//所以在线程0结束后,立刻使用到线程1,导致main线程中的话没说完.线程1就开始唱歌
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 4; i++){
System.out.println(Thread.currentThread().getName() + ":唱歌中啦啦啦啦啦啦啦");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":唱完了");
});
System.out.println("游戏开始,规则为谁先站上站台上谁就开始唱歌");
//启动线程0与线程1开始游戏
thread0.start();
thread1.start();
System.out.println("争夺中~");
//我们规定此处线程0为第一顺位,实际上进程中的线程调度是完全随机的
System.out.println("是线程0先抢占到了站台,请线程0开始你的表演");
thread0.join();
//在main线程中调用线程0的join方法.main线程等待线程0执行完后才执行下面的语句
System.out.println("线程0结束了他的表演,请大家为他鼓掌.接下来是线程1为大家表演");
thread1.join();
//main线程调用线程1的join方法,main线程等到线程1执行完后才执行下面的语句
System.out.println("本次游戏结束,谢谢大家");
}
6.7当前执行中线程的获取方法
方法 | 说明 |
public static Thread currentThread(); | 返回当前线程对象的引用 |
在进程中,线程都是并发的.其调度是具有随机性的.我们并不知道目前正在执行的线程到底是哪一个.我们就可以用此方法来得到目前正在执行的线程.
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
7.线程的状态
状态state | 说明 |
NEW | 系统中的线程还没创建(还没start调用),只有一个线程实例 |
RUNNABLE | 线程正在执行,或线程即将开始工作 |
BLOCKED | 线程停止执行,并等待(由线程上锁导致) |
WAITING | 线程停止执行,并等待(由wait(),join()方法及其他导致) |
TIMED_WAITING | 线程停止执行,并等待(由wait(long)方法,join(long)方法及sleep()方法及其他导致) |
TERMINATED | 线程工作结束 |