JavaEE:Thread 类的基本用法
目录
1.继承Thread类:
2、实现Runnable接口
3、匿名内部类中创建Thread子类对象
4、匿名内部类中创建Runnable子类对象
5、lambda表达式创建Runnabl子类对象
二,Thread 类及常见方法
1.Thread的常见的构造方法
2.Thread 的几个常见属性
3.启动一个线程-start()
start()和run()的区别:
4.获取当前线程
5.等待一个线程-join()
6.休眠当前线程
7.线程的中断
1.定义一个标志位,作为线程结束的标志
2.使用标准库中自带的标志位
如何查看线程信息
一,创建线程的方法:
1.继承Thread类:
//创建一个自定义的线程类并继承标准库中的Thread类:
class MyThread extends Thread{
@Override
//重写父类的run方法
public void run() {
System.out.println("这是继承Thread类创建的线程");
}
}
public class Thread1 {
public static void main(String[] args) {
//创建实例
MyThread thread = new MyThread();
//调用start方法后才会在系统上创建一个线程
thread.start();
}
}
重写run()方法,run是Thread父类里已经有的方法,需要重写一下,run里面的逻辑,就是这个线程要执行的工作,创建子类,并且重写run方法,相当于"安排工作"
注意:创建一个MyThread实例,创建实例,并不会在系统中真的创建一个线程!!在调用start方法时,才是真正创建出一个新的线程,新的线程就会执行run里面的逻辑,一直直到run里面的代码执行完,新的线程就运行结束了.
一个进程中至少会有一个线程,其中默认的线程就是main方法所在的线程(也叫做主线程,JVM创建的)
main主线程和MyThead创建出来的新线程是一个"并发执行"(并发+并行)的关系!!
并发执行:两边同时执行,各执行各的
2、实现Runnable接口
//实现 Runnable 接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("这是实现Runnable接口创建的线程");
}
}
public class Thread2 {
public static void main(String[] args) {
//创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Runnable runnable = new MyThread2();
//需要将Runnable实例作为Thread构造方法的参数
Thread thread = new Thread(runnable);
thread.start();
}
}
Runnable runnable = new MyThread2();
Thread thread = new Thread(runnable);这里我们是把线程要干的活和线程本身分开了,
使用Runnabke来专门表示"线程要完成的工作"->为了解耦合
3、匿名内部类中创建Thread子类对象
public class Thread3 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("匿名内部类创建Thread子类对象");;
}
};
thread.start();
}
}
4、匿名内部类中创建Runnable子类对象
public class Thread4 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建Runnable子类对象");
}
});
thread.start();
}
}
5、lambda表达式创建Runnabl子类对象
public class Thread5 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println("lambda表达式创建Runnable子类对象");
});
thread.start();
}
}
二,Thread 类及常见方法
1.Thread的常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象并命名 |
【了解】Thread(ThreadGroupgroup,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
上表中第三个和第四个Thread的构造方法,可以给线程命名。我们先创建两个线程,一个是默认名,一个是自定义名:
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "这是一个自定义的线程");
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("hello world");
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
2.Thread 的几个常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
3.启动一个线程-start()
在创建一个线程实例后,如同我在上面说的并不会在操作系统上创建一个线程,只有当这个实例调用strat方法后,才会真正在操作系统上创建一个线程,然后该线程会去执行run方法中的代码,执行完run方法后,该线程就会被销毁。
调用 start 方法, 才真的在操作系统的底层创建出一个线程
start()和run()的区别:
我们可以看到start()和run()调用后的结果都是一样的但是有什么区别呢?
直接调用run并没有创建线程的,只是在原来的线程中运行的代码.
调用start,则是创建了线程,在新线程中执行代码(和原来的线程是并发的)
4.获取当前线程
通过类名调用currentThread()方法即可获取当前正在运行的线程的引用:
可以与常见属性相搭配来使用!
5.等待一个线程-join()
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
示例:计算一个全局变量在一个线程中自增一千万次所需要的时间:
public class CSDN {
static int count;
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 1000_0000; i++) {
count++;
}
},suan);
long beg = System.currentTimeMillis();//获取线程开始执行的时间
thread.start();
long end = System.currentTimeMillis();//获取线程结束执行的时间
System.out.println("总共用时:"+(end - beg) + "ms");
}
}
我们会发现:
此时我们的代码中有: thread main 两个线程,并且它们是并发执行的!!
当main执行wanThread.start()后,仍然会继续往后走.
意思就是当Thread开始执行时main线程同时也在执行,所以就直接计算时间的没有等到Thread执行完,所以我们需要main线程等到Thread执行完后再执行,就需要join()了
public class CSDN {
static int count;
public static void main(String[] args) throws InterruptedException {
long beg = System.currentTimeMillis();//获取线程开始执行的时间
Thread thread = new Thread(()->{
for (int i = 0; i < 1000_0000; i++) {
count++;
}
},"T");
thread.start();
thread.join();
long end = System.currentTimeMillis();//获取线程结束执行的时间
System.out.println("总共用时:"+(end - beg) + "ms");
}
}
在main中调用Thread.join()效果就是让main线程阻塞等待,直到Thread执行完毕后main再执行!
6.休眠当前线程
方法 | 说明 |
sleep(long millis) | 休眠当前线程millis毫秒 |
sleep(long millis,int nanos) | 休眠当前线程misslis毫秒,nanos纳秒 |
sleep方法也是一个静态方法,需要通过类名进行调用。作用是让当前线程休眠一段时间后再继续往后执行,调用sleep方法也需要处理InterruptedException这个异常。
示例: 让main线程休眠8秒再往后执行。
public class CSDN {
public static void main(String[] args) throws InterruptedException {
long beg = System.currentTimeMillis();
Thread.sleep(8000);
long end = System.currentTimeMillis();
System.out.println("main线程休眠时间:"+(end-beg)+"ms");
}
}
调用sleep方法后线程会进入阻塞状态,休眠完8000ms后线程才会恢复到就绪状态,状态之间的切换也会消耗时间,因此线程的实际休眠时间会大于参数设置的时间。
7.线程的中断
run方法执行玩了,线程就结束了,也没有办法,让其提前一点结束呢?
通过线程中断的方法来进行的(本质上仍是让run方法尽快结束,而不是让run执行一般,强制结束)
取决与run里面的代码是如何实现的
1.定义一个标志位,作为线程结束的标志
public class CSDN {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t 线程执行完了");
});
t.start();
try {
//让其三秒钟之后结束
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("设置让 t 线程结束!");
}
}
2.使用标准库中自带的标志位
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("T线程结束");
},"T");
thread.start();
Thread.sleep(3000);
thread.interrupt();//设置标志位为true
System.out.println("设置标志位让T线程提前结束");
}
上述代码中的isInterrupted()方法是判断当前线程是否被中断,线程在正常运行时,该方法的返回值是false,即未被中断。而interrupt()方法则是将当前线程设置为中断状态,即将isInterrupted()方法的返回值设置为true。
从上图中可以发现,将标志位设置为True后,T线程在抛出一个异常信息后继续循环打印“hello thread”,线程并没有结束。
因为thread在调用interrupt()方法时,T线程存在两种状态:
(1)运行状态
此时调用interrupt()方法,可以顺利地中断进程。
(2)阻塞状态
T线程在调用sleep()方法后就会进入阻塞状态,此时调用interrupt()方法,不会设置标志位为True,而是会把T线程从阻塞状态提前唤醒,抛出InterruptedException异常。
所以我们要想中断进程,只需要在抛出异常时加一个break即可:
在Java中,中断线程并不一定是强制执行的,而是由线程自身进行判定处理(取决于代码是如何实现的),一般有三种处理方式:
(1)立即结束:直接break
(2)不予理会:不作处理,继续执行run方法的代码
(3)稍后结束:休眠一段时间再break
public class CSDN {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
// [方式一] 立即结束线程
break;
// [方式二] 啥都不做, 不做理会. 线程继续执行
// [方式三] 线程稍后处理
// Thread.sleep(1000);
// break;
}
}
System.out.println("t 线程执行完了");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println("设置让 t 线程结束!");
}
}
如何查看线程信息
打开jconsole后,选择本地进程,找到正在运行的线程所在的类,点击连接:
咱们自己看看自己写的不打紧,可以无视风险(滑稽)
黑夢: 大佬的文章让我对这领域的技术问题有了更深入的了解,尤其是大佬提到的那些“坑点”,我相信能够在实际应用中避免或解决很多问题。谢谢大佬的分享,期待大佬的更多精彩文章,让我们共同学习、进步。
程序猿进阶: 网络协议的作用是啥?
2301_77704139: 好棒,真的救命了。太爱了,谢谢您!
呼啦啦啦啦啦啦啦啦: 优质好文,博主的文章细节很到位,兼顾实用性和可操作性,感谢博主的分享,文章思路清晰,图文并茂,详略得当,三连支持,期待博主持续输出好文!
黑夢: 文章写的很详细,条理清晰,很容易看进去,学到了很多知识,感谢博主分享,支持博主