Java-线程的使用
一、创建线程
方式一:继承Thread类,重写run方法;
class Cat extends Thread{//继承Thread类
@Override
public void run() {//重写run方法,实现自己的业务逻辑
//super.run();
//父类Thread类实现了Runnable接口中的run方法
System.out.println("当前线程:" + Thread.currentThread().getName());
int count = 0;
while(count<=80) {
System.out.println("喵喵,我是小猫咪。====" + count);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Thread02{
public static void main(String[] args) {
/**
* 1.Thread.java程序运行起来后,就是一个进程;
* 2.进程先启动main线程;
* 3.main线程中又启动了一个新的子线程cat:
* 1)子线程cat运行的同时 ,main线程也在继续往下执行;
* 4.当主线程结束后:
* 1)若还有子线程在运行,进程不会结束;
* 2)只有当所有线程都结束后,进程才会结束;
*/
Cat cat = new Cat();
cat.start();//启动一个线程
for (int i = 0; i < 50; i++) {
System.out.println("main线程执行====" + i);
}
}
}
方式二:实现Runnable接口,重写run方法;
class A implements Runnable{
@Override
public void run() {
int count = 0;
while(count < 10){
count++;
System.out.println("hi == " + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Thread03{
public static void main(String[] args) {
A a = new A();
new Thread(a).start();
//使用了设计模式中的代理模式,Thread相当于一个代理,Runnable接口中没有start()方法;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("等价于这样!");
}
}).start();
}
}
问题:为什么启动线程是调用start(),而不是直接调用run()方法;
- 1.直接在main线程中调用run()方法将不会创建新的子线程,可以通过打印线程名称证明;
- 2.start()方法实现原理:
- 3.在JVM中,start()方法调用了start0()方法,start0()方法在不同的操作系统中有不同的实现(底层是C/C++实现的);
- 4.因此,真正实现多线程的是start0()方法,而不是start();
两种方式的区别
-
没有本质上的区别;
-
实现Runnable接口方式:
- 更适合多个线程共享同一个资源的情况;(即让多个线程执行同一个run方法)
- 避免了单继承的限制(如果继承了Thread类就不能再去继承其他类了);
-
因此建议使用实现Runnable接口方式来创建线程。
-
线程类(即继承了Thread的类)中所有的变量和常量都是线程共享的。为了不导致线程安全问题,应该避免修改这些共享的变量,或者使用synchronized关键字等方案来保证线程安全。
二、终止线程
两种情况
- 情况1:当线程完成任务后,会自动退出;
- 情况2:使用变量来控制run()方法退出,从而终止线程,即通知方式;
案例
// 需求:启动一个线程t, 要求在main线程中去停止线程t;
class A implements Runnable{
private boolean loop = true;//控制run()方法退出的变量
private int n = 0;
@Override
public void run() {
while(loop) {
System.out.println("线程: " + Thread.currentThread().getName() + "执行中。。。" + "====" + (++n));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop) {//对外提供set方法,改变loop的值,从而控制run方法退出
this.loop = loop;
}
}
public class Thread_stop01 {
/**
* 需求:启动一个线程t, 要求在main线程中去停止线程t;
* 思路:使用变量控制run()方法退出;
*/
public static void main(String[] args) {
A a = new A();
Thread t = new Thread(a);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("线程: " + Thread.currentThread().getName() + "执行中。。。" + "====" + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(i == 14)
break;
}
a.setLoop(false);
}
}
三、线程常用方法
第一组
- **setName() ** :设置线程名称;
- **getName() ** :返回线程名称;
- **start() ** :启动线程,JVM调用该线程的start0()方法;
- **run() ** ://调用线程类对象的run()方法;
- **setPriority() ** :更改线程的优先级;
- **getPriority() ** :返回线程的优先级;
- **sleep() ** :使当前正在执行的线程暂停执行(休眠)一定毫秒数;
- **interrupt() ** :中断线程;
第二组
-
**yield() ** :线程的“礼让”,该线程“礼让”其他线程先执行;
- 但是,礼让的时间不确定,因此也不一定成功;(比如CPU资源并不紧张时,可以进行兼顾,则不会礼让)
1.当一个线程调用 yield() 方法时,它会释放 CPU 资源,并将自己置于就绪状态,让系统调度器重新选择一个线程来执行。 2.如果没有其他线程处于就绪状态,那么当前线程将继续执行。 3.如果有其他线程处于就绪状态,那么系统调度器将会选择其中一个线程来执行,而当前线程则重新进入就绪状态,等待下一次被系统调度器选择执行。 4.需要注意的是,调用 yield() 方法并不会让线程进入阻塞状态,因此它不会释放锁或者资源。此外,yield() 方法也不能保证当前线程被重新选择执行的机会,因为它只是一种建议,而不是强制性的要求。
-
**join() ** :线程的插队;
- 插队的线程,一旦插队成功,则肯定先执行完插入线程的所有任务;
下面是 join() 方法的执行过程: 当线程 A 调用线程 B 的 join() 方法时,线程 A 将会进入阻塞状态,并等待线程 B 终止。 1.如果线程 B 已经终止了,那么线程 A 将会从阻塞状态中恢复,并继续执行下去。 2.如果线程 B 没有终止,那么线程 A 将会等待,直到线程 B 终止或者等待的时间到达指定的毫秒数。 3.如果线程 B 终止了,那么线程 A 将会从阻塞状态中恢复,并继续执行下去。 4.如果线程 B 没有在指定的时间内终止,那么线程 A 将会从阻塞状态中恢复,并继续执行下去。 需要注意的是,当线程 A 调用线程 B 的 join() 方法时,如果线程 B 没有启动,那么线程 A 将会一直阻塞,直到线程 B 启动并终止。 此外, join() 方法也可以带一个参数,用于指定线程等待的时间,如果线程 B 在等待的时间内没有终止,那么线程 A 将会从阻塞状态中恢复,并继续执行下去。
用户线程和守护线程
-
用户线程
- 也叫工作线程,当线程的任务执行完成时结束或者以通知方式结束;
-
守护线程
- 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
- 最常见的守护线程:垃圾回收机制;
//将一个线程设置为守护线程 MyDeamonThread myDeamonThread = new MyDeamonThread(); //设置为守护线程,再启动 myDeamonThread.setDaemon(true); myDeamonThread.start();