Java多线程的创建与Thread类的方法及使用
- 🍎一.Thread类的属性与方法
- 🍇1.1什么是线程
- 🍇1.2Thread类的基础常见的构造方法
- 🍇1.3Thread的常见属性
- 🍇1.4Thread类常用的基础方法
- 🍎二.Java线程的创建
- 🍇2.1继承Thread类来创建线程
- 🍇2.2继承Runnable接口创建线程
- 🍇2.3使用内部类创建线程
- 🍇2.4使用Runnable作为对象来创建线程
- 🍇2.5使用Lambda创建线程
- 🍎三.Java多线程并发基础实现与Thread类常用方法
- 🍇3.1多线程并发的简单实现
- 🍇3.2多线程与单线程效率的区别
- 🍇3.3查看Java线程内部基础属性
- 🍇3.4线程的中断
- 🍇3.5线程等待
- 🍇3.6run方法与start方法的区别(面试问题)
🍎一.Thread类的属性与方法
🍇1.1什么是线程
一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行
着多份代码.
举个例子如下场景:
一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
🍇1.2Thread类的基础常见的构造方法
序号 | 方法 | 解释 |
---|---|---|
1 | Thread() | 创建线程对象 |
2 | Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
3 | Thread(String name) | 创建线程对象,并命名 |
4 | Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
5 | 【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
Thread t1 = new Thread();
//我们需要创建一个类来继承Runnable类,在实例化,下文在创建我会给读者朋友演示的
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
🍇1.3Thread的常见属性
属性 | 获取方法 |
---|---|
ID | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
🍇1.4Thread类常用的基础方法
方法名 | 解释 |
---|---|
public void run() | 该方法用来封装线程运行时执行的内容 |
public synchronized void start() | 线程创建并执行run方法 |
public static native void sleep(long millis) throws InterruptedException | 使线程休眠millis毫秒(我们需要处理抛异常也可以用try catch) |
public final void join() throws InterruptedException | 等待线程结束(在哪个线程中调用哪个对象的join方法,哪个线程就等待哪个对象) |
public final synchronized void join(long millis) throws InterruptedException | 等待线程结束,()内可以添加你想等待的ms时间最多等待millis毫秒 |
public final synchronized void join(long millis, int nanos) throws InterruptedException | 指定最多等待时间等待线程,精确到纳秒 |
public void interrupt() | 中断线程对象所关联的对象,如果线程在休眠(阻塞状态)会抛出异常通知,否则设置中断标志位break |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后会清除线程的中断标志位 |
public boolean isInterrupted() | 判断当前线程的中断标志位是否设置,调用后不会影响线程的标志位 |
public final synchronized void setName(String name) | 修改线程对象名称 |
public static native Thread currentThread() | 获取当前线程对象 |
🍎二.Java线程的创建
🍇2.1继承Thread类来创建线程
//MyThread来继承Thread类并且重写了run方法
class MyThread extends Thread{@Overridepublic void run(){System.out.println("hello 1");}}
public class dome1 {public static void main(String[] args) {Thread thread = new MyThread();//记住只有执行start方法是线程才上真正的创建成功thread.start();}
}
🍇2.2继承Runnable接口创建线程
//MyRunnable来继承Runnable接口,并且在run方法中重写要执行的内容
class MyRunnable implements Runnable{@Overridepublic void run(){System.out.println("hellow 3");
}
public class dome3 {public static void main(String[] args) {Thread thread3 = new Thread(new MyRunnable());thread3.start();}
}
🍇2.3使用内部类创建线程
public class demo4 {public static void main(String[] args) {Thread thread4 = new Thread(){@Overridepublic void run(){System.out.println("hello 4");}};thread4.start();}
}
🍇2.4使用Runnable作为对象来创建线程
public class deom5 {public static void main(String[] args) {Thread thread5 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello 5");}});thread5.start();}
}
🍇2.5使用Lambda创建线程
public class deom5 {public static void main(String[] args) {Thread thread5 = new Thread(()-> {System.out.println("hello 5");});thread5.start();}
}
🍎三.Java多线程并发基础实现与Thread类常用方法
🍇3.1多线程并发的简单实现
public class demo7 {public static void main(String[] args) {Thread thread7 = new Thread(new Runnable(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("Thread线程在执行");try {Thread.sleep(1000);//休眠一秒,每过一秒打印一次} catch (InterruptedException e) {e.printStackTrace();}}}});thread7.start();for (int i = 0; i < 10; i++) {System.out.println("main线程在执行");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
从上面的运行结果可以看出一个问题,因为thread线程与main线程都是每打印一句语句线程休眠1秒,两个线程唤醒的先后顺序是随机的,这也是java多线程中的一个“万恶之源”,这个问题给我们带来了很多麻烦,后续的博客我会细说
🍇3.2多线程与单线程效率的区别
public class demo8 {
//创建一个不可更改成员实数countpublic static final long count = 10_0000_0000;//多线程使用方法public static void thread() throws InterruptedException {//获取开始执行时间戳long start = System.currentTimeMillis();Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (int i = 0; i < count; i++) {a++;}}});thread1.start();Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {int b = 0;for (int i = 0; i < count; i++) {b++;}}});thread2.start();thread1.join();//需要抛异常thread2.join();//获取结束执行时间戳long end = System.currentTimeMillis();System.out.println("多线程执行时间:" + (end - start) + "ms");}//单线程方法public static void one(){//获取开始执行时间戳long start = System.currentTimeMillis();int a =0;for (int i = 0; i < count; i++) {a++;}int b =0;for (int j = 0; j < count; j++) {b++;}//获取结束执行时间戳long end = System.currentTimeMillis();System.out.println("单线程执行时间:" + (end - start) + "ms");}public static void main(String[] args) throws InterruptedException {//多线程thread();//因为thread方法执行了join方法所以要在main抛异常//单线程one();}
}
我们发现在执行大量计算执行结束后多线程的效率是比单线程执行的效率要快很多
🍇3.3查看Java线程内部基础属性
在我们下载好的jdk文件打开bin文件
找到这个文件双击
点击我们执行的文件
点击链接后,弹出这个页面,点击不安全链接
点击右上角线程这样我们就可以看到Java线程的一些属性
🍇3.4线程的中断
方法一:
public class deom9 {private static boolean quite = false;public static void main(String[] args) throws InterruptedException {Thread thread9 = new Thread(new Runnable() {@Overridepublic void run() {//我们让每次相隔1秒打印一次while (!quite) {System.out.println("一个不起眼的线程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});thread9.start();//main和线程一起执行,这样我们可以先限制5秒这样就可以先打印5次在终止了Thread.sleep(5000);quite = true;}
}
但是该方法是不够严谨的,有些场景可能达不到预期的效果,最优的做法就是调整线程对象或者线程类中的自带标志位
优化版本
public class demo10 {public static void main(String[] args) throws InterruptedException {Thread thread10 = new Thread(()->{while (!Thread.interrupted()) {System.out.println("一个不起眼的线程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread10.start();//休眠5秒Thread.sleep(5000);//使用interrupt方法修改线程标志位,使其中断thread10.interrupt();}
}
我看到当我们进行5秒后的中断指令后发现线程还在继续执行,抛出一个InterruptedException异常后,线程没有中断,而是继续运行,原因是interrupt方法遇到因为调用 wait/join/sleep 等方法而阻塞的线程时会使sleep等方法抛出异常,并且中断标志位不会修改为true,这时我们的catch语句里面值输出了异常信息并没有去中断异常,所以我们需要在catch语句中加上线程结束的收尾工作代码和退出任务循环的break语句就可以了。
这样我们就可以看到我们已经成功中断线程执行了
方法二:
首先使用currentThread方法获取线程对象,然后再调用该对象中的isterrupted方法获取该对象的中断标志位代替我们自己所写的isQuit标志位,然后等该线程运行一段时间后使用interrupt方法改变标志位,中断线程,写出如下代码,看看能不能达到预期效果:
我们在实际使用中建议使用方法二
public class demo10 {public static void main(String[] args) throws InterruptedException {Thread thread10 = new Thread(()->{while (!Thread.currentThread().interrupted()) {System.out.println("一个不起眼的线程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println("线程终止");break;}}});thread10.start();//休眠5秒Thread.sleep(5000);//使用interrupt方法修改线程标志位,使其中断thread10.interrupt();}
}
🍇3.5线程等待
像上面的计算自增20亿次的例子就需要线程等待join方法,main线程需要等两个线程运行完毕后才能计算计算结束时的时间戳。
我们来假设几个线程,线程A表示调用join方法的线程,线程B表示join方法来自B线程对象,那么在A线程使用B.join方法,那就是A线程等待B线程结束
🍇3.6run方法与start方法的区别(面试问题)
当我们调用run方法就是单纯地调用了Thread对象中的一个重写普通方法而已,并没有创建一个新线程来执行run方法,而是通过main线程来执行的run方法,而使用start方法,会创建一个新线程并执行run方法。
注意:只有在执行线程的start方法时才是真正的完成创建线程