线程的控制方法

一、Java 中的线程状态转换

【注】不是 start 之后就立刻开始执行,只是就绪了(CPU可能正在运行其他的线程)。只有被 CPU 调度之后,线程才开始执行,当 CPU 分配给的时间片到了,又回到就绪状态,继续排队等候。

二、线程控制的基本方法

在这里插入图片描述

  1. isAlive():判断线程是否还活着。start 之后,终止之前都是活的。
  2. getPriority():获得线程的优先级数值。
  3. setPriority():设置线程的优先级数值(线程是有优先级别的)。
  4. Thread.sleep():将当前线程睡眠指定毫秒数。
  5. join():调用某线程的该方法,将当前线程与该线程合并,也即等待该线程结束后,再恢复当前线程的运行状态(比如在线程B中调用了线程A的 join(),直到线程A执行完毕后,才会继续执行线程B)。
  6. yield():当前线程让出 CPU,进入就绪状态,等待 CPU 的再次调度。
  7. wait():当前线程进入对象的 wait pool。
  8. notify()/notifyAll():唤醒对象的 wait pool 中的一个/所有的等待线程。

三、isAlive():判定线程是否处于活动状态

//判定线程是否处于活动状态:就绪状态、运行状态、阻塞状态
public class Demo2 {
    public static void main(String[] args) {
       new ThreadB().start();
    }
}
class ThreadB extends Thread {
	@Override
	public void run() {
		System.out.println("检测线程是否是活动状态");
		System.out.println(Thread.currentThread().isAlive());
	}
}

四、setPriority():设置线程的优先级

//设置线程优先级 MAX_PRIORITY:最大为10 
//MIN_PRIORITY:最小为1 
//DEFAULT_PRIORITY:默认为5
public class Demo4 {
	public static void main(String[] args) {
		ThreadD td = new ThreadD();
		ThreadE te = new ThreadE();
		td.start();// 谁先启动,谁抢占资源的概率越大
		te.start();
		td.setPriority(Thread.MAX_PRIORITY);// 设置优先级为最大,10
		te.setPriority(Thread.MIN_PRIORITY);// 设置优先级为最小,1
	}
}
class ThreadD extends Thread {
	@Override
	public void run() {
		// 默认优先级为5
		// System.out.println(Thread.currentThread().getPriority());
		for (int i = 0; i < 5; i++) {
			System.out.println("ThreadD j---" + i);
		}
	}
}
class ThreadE extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("ThreadE i---" + i);
		}
	}
}
ThreadD j---0
ThreadE i---0
ThreadE i---1
ThreadE i---2
ThreadE i---3
ThreadE i---4
ThreadD j---1
ThreadD j---2
ThreadD j---3
ThreadD j---4

五、sleep():将当前线程睡眠指定毫秒数

  1. 可以调用 Thread 的静态方法:
    public static void sleep(long millis) throws InterruptedException在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响,该线程不丢失任何监视器的所属权。InterruptedException - 如果任何线程中断了当前线程,当抛出该异常时,当前线程的中断状态被清除。

  2. 由于是静态方法,sleep() 可以由类名直接调用Thread.sleep(.....);

  3. Thread.Sleep(0)作用:触发操作系统立刻重新进行一次CPU竞争。结果或许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。大循环里面经常写它,以给其他线程比如Paint线程获得CPU控制权的机会,界面就不会假死在那里。

class TestThread {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        try {                   // 在哪个线程中调用 Sleep,就让哪个线程睡眠
            Thread.sleep(8000); // 主线程睡8秒后,打断子线程
        } catch (InterruptedException e) {
        }
        thread.interrupt();     // 打断子线程
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        while(true){
            System.out.println("=== "+ new Date()+" ===");
            try {
                sleep(1000); // 每隔一秒打印一次日期
            } catch (InterruptedException e) {
                return;
            }
        }
    }  
}

运行结果:

=== Tue May 09 11:09:43 CST 2017 ===
=== Tue May 09 11:09:44 CST 2017 ===
=== Tue May 09 11:09:45 CST 2017 ===
=== Tue May 09 11:09:46 CST 2017 ===
=== Tue May 09 11:09:47 CST 2017 ===
=== Tue May 09 11:09:48 CST 2017 ===
=== Tue May 09 11:09:49 CST 2017 ===
=== Tue May 09 11:09:50 CST 2017 ===

子线程每隔一秒打印系统日期,主线程睡眠8秒后,打断子线程,子线程结束。此例还可用一种简单、粗暴、好用的方法中断子线程:

class TestThread {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        try {                    // 在哪个线程中调用 Sleep,就让哪个线程睡眠
            Thread.sleep(5000); // 主线程睡8秒后,打断子线程
        } catch (InterruptedException e) {
        }
        thread.flag = false; // 打断子线程
    }
}
class MyThread extends Thread {
    boolean flag = true;
    @Override
    public void run() {
        while(flag){
            System.out.println("=== "+ new Date()+" ===");
            try {
                sleep(1000); // 每隔一秒打印一次日期
            } catch (InterruptedException e) {
            }
        }
    }
}

运行结果:

=== Tue May 09 12:21:24 CST 2017 ===
=== Tue May 09 12:21:25 CST 2017 ===
=== Tue May 09 12:21:26 CST 2017 ===
=== Tue May 09 12:21:27 CST 2017 ===
=== Tue May 09 12:21:28 CST 2017 ===

六、join():合并某个线程,相当于方法的调用

class TestThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("childThread");
        myThread.start();
        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i = 1; i <= 4; i++){
            System.out.println("I am the mainThread");
        }
    }
}
class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for(int i = 1; i <= 4; i++){
            System.out.println("I am " + getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
    }
}

运行结果:

I am childThread
I am childThread
I am childThread
I am childThread
I am the mainThread
I am the mainThread
I am the mainThread
I am the mainThread

等待线程结束后,再恢复当前线程的运行。

七、yield():让出 CPU,当前线程进入就绪状态队列等待,给其他线程执行的机会(就让很小的一个时间片段)。

class TestThread {
    public static void main(String[] args) {
        MyThread my1 = new MyThread("t1");
        MyThread my2 = new MyThread("t2");
        my1.start();
        my2.start();
    }
}
class MyThread extends Thread {
    public MyThread(String s) {
        super(s);
    }
    @Override
    public void run() {
        for(int i = 1; i <= 20; i++){
            System.out.println(getName()+":"+i);
            if(i % 5 == 0) {
                Thread.yield(); // 当前线程让出 CPU 一小会儿
            }
        }
    }  
}

八、wait():它会释放掉对象的锁

当前的线程必须拥有当前对象的 monitor,也即 lock,就是锁。线程调用 wait(),释放它对锁的拥有权,使得当前线程必须要等待。然后等待另外的线程调用 notify() 或者 notifyAll() 来通知它,这样它才能重新获得锁的拥有权和恢复执行。要确保调用 wait() 的时候拥有锁,即:wait() 的调用必须放在synchronized 方法或 synchronized 块中。

区别:
1️⃣当线程调用了 wait() 时,它会释放掉对象的锁。
2️⃣另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但在睡眠的过程中是不会释放掉对象的锁的。
3️⃣wait() 和 notify()/notifyAll() 在释放对象锁的区别在于:wait() 立即释放,notify()/notifyAll() 则会等待线程剩余代码执行完毕才会释放。

九、notify()/notifyAll():唤醒对象的 wait pool 中的一个/所有等待线程

notify() 会唤醒一个等待当前对象的锁的线程。如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关(线程等待一个对象的锁是由于调用了 wait() 中的一个)。被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。

notify() 应该是被拥有对象的锁的线程所调用This method should only be called by a thread that is the owner of this object's monitor。换句话说,和 wait() 一样,notify() 调用必须放在 synchronized 方法或 synchronized 块 中。

为何 wait()、notify()/notifyAll() 要在同步块中被调用?

这是 JDK 强制的,wait() 和 notify()/notifyAll() 在调用前都必须先获得对象的锁。

十、终止线程的方法

  1. 正常运行结束,线程自动结束。

  2. 使用退出标志退出线程
    一般 run() 执行完,线程就会正常结束。然而,常常有些线程是伺服线程,需要长时间运行,只有在外部某些条件满足的情况下,才能被关闭。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean型的标志,通过该标志控制 while 循环是否退出。

public class ThreadSafe extends Thread { 
    public volatile boolean exit = false;  
        public void run() {  
        while (!exit){ 
            //do something 
        } 
    }  
}

定义默认值为 false 的退出标志 exit,当其为 true 时,while 循环退出。在定义 exit 时,使用Java关键字volatile修饰,确保同一时刻只能由一个线程修改 exit 的值。

  1. 使用interrupt()来中断线程(两种情况)

a.线程处于阻塞状态
如使用了 sleep,同步锁的 wait,socket 的 receiver、accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt() 时,阻塞的方法会抛出 InterruptedException,通过代码捕获该异常,然后 break 跳出循环状态,从而有机会结束线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的,一定要先捕获InterruptedException之后通过 break 来跳出循环,才能正常结束 run 方法。

b.线程未处于阻塞状态
使用 isInterrupted() 判断线程的中断标志来退出循环。当使用 interrupt() 时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。

public class ThreadSafe extends Thread {
	public void run() {
    	while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出 
            try{ 
                Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出 
            }catch(InterruptedException e){ 
                e.printStackTrace(); 
                break;//捕获到异常之后,执行break跳出循环 
            } 
        } 
    }  
} 
  1. stop 方法终止线程(线程不安全)
    直接使用 thread.stop() 来强行终止线程,但是该方法很危险,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果。不安全主要是:thread.stop() 调用之后,创建子线程的线程就会抛出 ThreadDeatherror,并且会释放子线程所持有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop() 后导致了该线程所持有的锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法。

十一、一个线程变为一个对象的锁的拥有者的三种方法

  1. 执行该对象的 synchronized 实例方法。
  2. 执行该对象的 synchronized 语句块(语句块锁的是该对象,如 synchronized(object))
  3. 对于 Class 类的对象,执行该类的 synchronized、static方法。

十二、Daemon Thread(守护线程)

Java 中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)。用户线程即运行在前台的线程,而守护线程是运行在后台的线程。守护线程的作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当JVM检测仅剩一个守护线程,而用户线程都已经退出运行时,JVM 就会退出,因为没有了被守护者,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,JVM 就不会退出。

守护线程必须在它的 start() 之前通过 setDaemon(true) 进行设置。

//守护线程:在线程启动前设置setDaemon(true)
public class Demo6 {
	public static void main(String[] args) throws Exception {
		ThreadH th = new ThreadH();
		th.setDaemon(true);// 将当前线程设置为守护线程
		th.start();
		for (int i = 0; i < 10; i++) {
			Thread.sleep(1000);
			System.out.println("主线程。。。" + i);
		}
	}
}
class ThreadH extends Thread {
	int i;
	@Override
	public void run() {
		while (true) {
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("Hello,---" + ++i);
		}
	}
}

十三、如何检测一个线程是否持有对象锁

Thread 类提供了一个 holdsLock(Object obj),当且仅当对象 obj 的监视器被某条线程持有的时候才会返回 true。这是一个 static 方法,这意味着“某条线程”指的是当前线程

十四、如何唤醒一个阻塞的线程

如果线程是因为调用了 wait()、sleep() 或者 join() 而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它。如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

十五、Thread 类中的 start() 和 run() 的区别

1️⃣start() 来启动线程,真正实现了多线程运行。该方法会在内部调用 Runnable 接口的 run(),以在单独的线程中执行 run() 中指定的代码。此时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。调用 start() 启动线程,此时该线程处于就绪状态,并没有运行。然后通过此 Thread 类调用 run() 来完成其运行操作的,这里 run() 称为线程体,它包含了要执行的这个线程的内容,run() 运行结束,此线程终止。然后 CPU 再调度其它线程。start() 启动线程执行以下任务:

● 它统计了一个新线程
● 线程从 New State 移动到 Runnable 状态。
● 当线程有机会执行时,它的目标 run() 将运行。

2️⃣run() 当作普通方法的方式调用。程序顺序执行,要等待 run 方法体执行完毕后,才可继续执行下面的代码;程序中只有主线程——这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。

调用 start() 才可启动线程,而 run() 只是 thread 的一个普通方法调用,还是在主线程里执行。把需要并行处理的代码放在 run() 中,start() 启动线程将自动调用 run() ,这是由JVM的内存机制规定的。并且 run() 必须是 public 访问权限,返回值类型为 void。

3️⃣区别
①方法的定义
start() 在java.lang.Thread类中定义;而 run() 在java.lang.Runnable接口中定义,必须在实现类中重写。

②新线程创建
当程序调用 start() 时,才会表现出多线程的特性,此时创建一个新线程,然后执行 run()。如果直接调用 run(),则不会创建新的线程,run() 将作为当前调用线程本身的常规方法调用执行,并且不会发生多线程。示例:

class MyThread extends Thread {
   public void run() {
     System.out.println("\n");
     System.out.println("当前线程的名称: "
       + Thread.currentThread().getName());
     System.out.println("run()方法调用");
    }
}
class demo {
   public static void main(String[] args) {
     MyThread t = new MyThread();
     t.start();
   }
}

当调用线程类实例的 start() 时,会创建一个新的线程,默认名称为 Thread-0,然后调用 run(),并在其中执行所有内容。新创建的线程。

现在,尝试直接调用 run() 而不是 start():

class MyThread extends Thread {
   public void run() {
     System.out.println("\n");
     System.out.println("当前线程的名称: "
       + Thread.currentThread().getName());
     System.out.println("run()方法调用");
   }
}
class GeeksforGeeks {
   public static void main(String[] args) {
     MyThread t = new MyThread();
     t.run();
   }
}

当调用 MyThread 类的 run() 时,没有创建新线程,并且在当前线程即主线程上执行 run()。因此,没有发生多线程。run() 是作为正常函数被调用。

③多次调用
start() 不能多次调用,否则抛出java.lang.IllegalStateException。run() 可以进行多次调用,因为它只是一种正常的方法调用。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值