文章目录
一、Thread 类
了解如何使用Thread 类实现多线程之后,继续学习Thread 类实现多线程之后的相关功能及方法。其中的一些方法是 static 的,由类名直接调用,通常都是在那个线程中执行,这些静态的方法就指定的那个线程。
1、操作线程名称的方法
- 构造方法(实现 Runnable 接口时候使用)
- public Thread(Runnable target,String name); 创建线程时设置线程名称。
- 成员方法
- public final void setName(String name); 设置线程的名称。
- public final String getName(); 获取线程的名称。
-
Demo 代码示例:
public class TestThread extends Thread{ @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.println("我正在编写多线程代码"+ i); } } //程序主线程 main 线程 public static void main(String[] args) { //创建子类对象 TestThread thread = new TestThread(); // 设置线程名称 thread.setName("姚青新创建的线程"); //调用 start() 方法开启线程 thread.start(); for (int i = 1; i <= 10; i++) { System.out.println("我正在学习多线程"+ i); } // 获取线程名称 System.out.println(thread.getName()); } }
运行结果:
2、获取当前正在执行的线程对象:currentThread()
-
public static Thread currentThread(); 返回当前正在执行的线程对象
- 获取当前线程对象
- Thread.currentThread();
- 获取当前线程对象名称
- Thread.currentThread().getName();
- 获取当前线程对象
-
Demo代码示例:
public class TestThread extends Thread{ @Override public void run() { // 获取start()方法创建出来的线程对象 System.out.println("start() 创建的线程对象:"+Thread.currentThread()); // 获取start()方法创建出来的线程对象名称 System.out.println("start() 创建的线程对象名称:"+Thread.currentThread().getName()); } public static void main(String[] args) { TestThread thread = new TestThread(); thread.setName("姚青新创建的线程"); thread.start(); //获取 主线程对象 System.out.println("主线程对象:"+Thread.currentThread()); // 获取main()主线程对象名称 System.out.println("主线程对象名称"+Thread.currentThread().getName()); } }
运行结果:
在使用这个方法的时候需要注意一点,该方法固定的写法就是 Thread.currentThread(); 放在那个线程中执行这个方法就是指定的那个线程。
这个写法主要作用就是获取当前线程的线程对象,获取线程对象之后还可以继续对该线程对象的一些状态进行操作。
3、线程休眠 :sleep()
- public static void sleep(long millis); 根据传入的时间参数让当前线程休眠
- 休眠以毫秒为单位:millis
Demo代码示例
public class TestThread extends Thread{
@Override
public void run() {
System.out.println("当前线程名称:"+Thread.currentThread().getName());
try {
// 将线程休眠五秒
Thread.sleep(5000);
System.out.println("将线程休眠五秒");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread()+":"+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.setName("姚青创建的新线程");
thread.start();
System.out.println("当前线程名称:"+Thread.currentThread().getName());
}
}
运行结果:被指定的线程休眠了五秒,五秒后恢复运行
1、Object 类的 wait(), notify(), notifyAll() 等方法介绍
线程休眠的功能 Object 类的 wait(), notify(), notifyAll() 等方法也同意具备。
-
public final void wait(): 让当前线程进入休眠状态,同时,wait()也会让当前线程释放它所持有的锁。当其他线程调用此对象的notify()方法或 notifyAll() 方法,当前线程就会被唤醒(进入“就绪状态”)。
-
public final native void wait(long timeout): 让当前线程处于休眠状态”,并且设置一个唤醒时间,当其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间,当前线程就会被唤醒(进入“就绪状态”)。
-
public final native void notify(): 唤醒当前正在休眠的线程,如果等待池中有多条线程的话就随机唤醒其中的一个线程;
-
public final native void notifyAll(): 唤醒等待池中所有的线程
wait(),notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用。因为wait()被唤醒时是需要释放锁的,但是如果没加锁就无法释放锁了,所有需要在同步代码块中先获取到锁在释放锁。
- sleep()和wait()方法的区别?
- sleep():必须指时间;不释放锁。
- wait():可以不指定时间,也可以指定时间;释放锁。
4、线程中断状态控制
- public final void stop(); 中断线程
- 使用 stop() 后该线程就停止了,不再继续执行,并且线程无法恢复,可能会造成线程不安全问题,不建议使用
- public void interrupt(); 中断线程
- 使用 interrupt() 会终止线程的状态,还会继续执行run方法里面的代码,且线程可以恢复,较为安全。线程最安全的终止状态是让程序运行完,线程自己停下来
- public boolean isInterrupted(); 用来判断当前线程是否为中断状态
- 如果当前线程是中断状态就返回 true ,不是中断状态返回 false
- public static boolean interrupted(); 用来恢复线程的中断状态
- 检验当前线程是否是中断状态,如果是中断状态,返回 true ,并将线程重新恢复成流通状态。如果不是中断状态,返回 false,直接退出方法。
- 检验当前线程是否是中断状态,如果是中断状态,返回 true ,并将线程重新恢复成流通状态。如果不是中断状态,返回 false,直接退出方法。
Demo 代码示例:
public class TestThread extends Thread{
@Override
public void run() {
System.out.println("当前线程名称:"+Thread.currentThread().getName());
try {
// 将线程休眠五秒
Thread.sleep(5000);
System.out.println("将线程休眠五秒");
} catch (InterruptedException e) {
// 如果线程休眠出现异常,就将线程中断
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
System.out.println("当前线程名称:"+Thread.currentThread().getName());
Thread.currentThread().interrupt();
System.out.println("将主线程中断");
//判断运行当前线程是否是中断状态
System.out.println("检验当前线程是否是中断状态:"+Thread.currentThread().isInterrupted());
// 将主线程重新连接
System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
}
}
运行结果:
5、观测线程状态 Thread.State 以及获取当前线程状态 getState()
在 Java 语言中,程序的线程定义 Thread 类中的 State 中,定义了六种线程状态
- Thread.State :线程状态观测,线程状态一共有下面六种:
- NEW :尚未启动的线程处于此状态
- RUNNABLE :正在 Java 虚拟机中执行的线程处于此状态
- BLOCKED :被堵塞等待监视器锁定的线程处于此状态
- WAITING: 正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING:正在等待另一个线程执行特定动作达到指定时间的线程处于此状态
- TERMINATED:已退出的线程处于此状态
Demo代码示例:
//Thread.State.上面六个中的一个:指定线程状态
- public State getState() :返回当前线程的线程状态信息
- 通过此方法,可以得到线程的各个执行状态
Demo代码示例:
public class SellTicket implements Runnable {
@Override
public void run() {
Thread thread = Thread.currentThread();
// 查看线程状态
Thread.State state = thread.getState();
System.out.println("第二次查看:"+state);
// 如果 此时线程的状态是 RUNNABLE ,就将线程休眠五秒
if(state == Thread.State.RUNNABLE){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// 如果休眠出现异常就见线程中断
thread.interrupt();
}
}
}
}
class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread thread = new Thread(st, "姚青");
// 查看线程状态
Thread.State state = thread.getState();
System.out.println("第一次查看:"+state);
// 启动线程
thread.start();
}
}
运行结果:
6、线程优先执行(线程加入):join()
-
public final void join(); 当通过线程对象调用该方法时,线程就会呈现堵塞状态,只有调用该方法的线程可以流通。直到线程对象的 run() 执行完毕,线程的堵塞状态结束。
- Demo 代码示例:
public class ThreadJoin extends Thread { @Override public void run() { for (int x = 0; x < 5; x++) { System.out.println(getName() + ":" + x); } } public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj1.setName("皮卡丘"); tj2.setName("可达鸭"); tj3.setName("呱呱"); tj1.start(); try { // 将线程切换成堵塞状态,只执行 tj1 线程,run() 执行完成之后,堵塞结束 tj1.join(); } catch (InterruptedException e) { e.printStackTrace(); } tj2.start(); tj3.start(); } }
运行结果:可以看出,皮卡丘是先执行的,当这个线程执行完成之后,其他两个线程同步执行。
7、线程延后执行(线程礼让): yield()
- public final void yield(); 线程礼让
- yield() 的作用就是让步,当多个线程同时执行时,调用该方法的线程会将当前的线程执行状态让出来,和其他线程处于同一个起跑线上。但是线程的执行顺序无法控制,可能是其他线程,也可能是调用 yield() 的线程。
- 使用该方法,当前线程会放弃现有的CPU资源,和其他线程一起去抢夺现有的CPU资源,但是这样做会让程序运行花费更多的时间。
Demo 代码示例:不调用 yield() 方法看运行的时间差
public class ThreadYield extends Thread{
int count = 0;
@Override
public void run() {
//获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
long beginTime =System.currentTimeMillis();
for (int i = 0 ;i<= 50000000; i++) {
//Thread.yield();
count = count +(i+1);
}
long endTime = System.currentTimeMillis();
// 用第二次获取到的时间距离减第一次获取到的时间距离
System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
}
public static void main(String[] args) {
ThreadYield thread = new ThreadYield();
thread.start();
}
}
运行结果:
Demo 代码示例:调用 yield() 方法看运行的时间差
public class ThreadYield extends Thread{
int count = 0;
@Override
public void run() {
//获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
long beginTime =System.currentTimeMillis();
for (int i = 0 ;i<= 50000000; i++) {
Thread.yield();
count = count +(i+1);
}
long endTime = System.currentTimeMillis();
// 用第二次获取到的时间距离减第一次获取到的时间距离
System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
}
public static void main(String[] args) {
ThreadYield thread = new ThreadYield();
thread.start();
}
}
运行结果:
通过两次运行结果可以看出程序运行的时间差别还是很大的。调用 yield() 运行的时间明显延长。
8、守护线程和用户线程的设置:setDaemon()
- public final void setDaemon(boolean on); 设置守护线程和用户线程
-
java 中线程分为两种类型:用户线程和守护线程。通过 Thread.setDaemon(false); 设置为用户线程;通过 Thread.setDaemon(true); 设置为守护线程。如果不设置次属性,默认为用户线程。
-
该方法必须在用户线程执行之前执行
-
用户线程和守护线程的区别:
- 主线程结束后,用户线程还会继续运行,JVM是存活状态;
- 主线程结束后,如果没有用户线程运行,守护线程和JVM便都结束运行。
- 主线程结束后,如果有用户线程运行,守护线程和JVM便继续为用户线程服务。
- 垃圾回收机制便是典型的守护线程,用户线程存在时,便会回收用户线程制造出来的垃圾,用户线程结束后,垃圾回收机制也结束。
-
-
Demo 代码示例:
public class ThreadsetDaemonDemo extends Thread {
@Override
public void run() {
for (int x = 0; x < 10; x++) {
System.out.println(getName() + ":" + x);
}
}
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("皮卡丘");
td2.setName("喷火龙");
// 设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("小智");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
运行结果:通过结果可以看出,用户线程一共执行了五次,守护线程也是执行了五次。但是其实守护线程是设置成10次的,因为用户线程执行结束了,所有守护线程自行离开,所有接下来的程序自然也就不继续执行了。
二、Runnable 接口
在创建多线程时候推荐使用实现 Runnable 接口的方式,该接口中只有一个 run() 的抽象方法,我们在使用时候只需要实现该抽线方法即可。然后通过实例化 Thread 类,调用 Thread 类的构造方法,然后将我们创建的线程类对象传入该构造方法,并且可以给该线程设置名称,然后调用 Thread 类对象调用 start() 创建和启动线程,从而实现多线程的过程。这是一种代理模式(静态代理模式),可以很好的降低程序的耦合。
并且因为接口可以多实现,这就避免了单继承的局限性,灵活便捷,方便同一个对象被多线程使用。
Demo 代码的复用示例:
public class TestRunnable implements Runnable{
//run()方法线程
@Override
public void run() {
// 获取新创建的线程名称
String str = Thread.currentThread().getName();
System.out.println("姚青创建的线程:"+str);
try {
// 将线程休眠4秒
Thread.sleep(4000);
System.out.println(Thread.currentThread().getName()+"休眠4秒");
} catch (InterruptedException e) {
// 如果线程休眠时出现线程异常,就将线程中断
Thread.currentThread().interrupt();
}
}
//程序主线程 main线程
public static void main(String[] args) {
// 创建 Runnable 接口实现类对象
TestRunnable tr = new TestRunnable();
// 创建线程对象,设置线程名称,并通过线程对象来开启线程,这种启动线程的方式是代理的方式
Thread thread = new Thread(tr, "姚青创建的线程");
thread .start();
// 获取当前线程的线程名称
String str = Thread.currentThread().getName();
System.out.println("主线程:" + str);
// 将当前线程中断
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().getName()+"线程中断了!");
// 判断当前线程是否是中断状态
if (Thread.currentThread().isInterrupted() == true) {
// 如果是中断状态就重新链接
Thread.interrupted();
System.out.println(Thread.currentThread().getName()+"线程重新链接了!");
}
}
}
运行结果: