线程相关实例方法
获取线程ID- getId
- 在一个Java应用程序中,有一个long型的全局唯一的线程ID生成器
threadSeqNumber
,每new出来一个线程就会自增一次,从0开始,并且赋值给线程的tid属性。 - 用户只能获取ID,不能执行一个线程的ID,这是Thread类内部自己完成的。
获取和设置线程的名字
获取线程名
- 通过
getName()
方法获取线程对象名
new Thread(){ @Override public void run() { System.out.println(this.getName());//Thread-0 } }.start();
- 通过
设置线程名
- 通过
构造函数
传入String类型名
new Thread("线程1"){ @Override public void run() { System.out.println(this.getName());//线程1 } }.start(); new Thread("线程2"){ @Override public void run() { System.out.println(this.getName());//线程2 } }.start();
//Lambda表达式的Runnable方式,Thread的构造函数 Thread t2 = new Thread(() -> System.out.println("线程5的执行方法体"),"线程5"); t2.start(); System.out.println(t2.getName());//线程5
- 通过
setName(String name)
方法设置
new Thread(){ @Override public void run() { this.setName("线程3"); System.out.println(this.getName());//线程3 } }.start();
Thread t1 = new Thread() { @Override public void run() { System.out.println(this.getName());//线程4 } }; t1.setName("线程4"); t1.start();
Thread t1 = new Thread(()-> System.out.println("线程4的执行方法体")); t1.setName("线程4"); t1.start(); System.out.println(t1.getName());//线程4 /* 线程4 线程4的执行方法体 */
- 通过
线程对象是否处于活动状态 - isAlive
t.isAlive()
测试线程t是否处于活动状态,只要线程启动并且没有终止
,方法返回值就是true
。start()
之前,线程不处于活动状态,之后就处于了活动状态。
获取当前线程的对象
Thread.currentThread() 静态方法,获取当前执行线程
, 主线程也可以获取//Runnable接口方式 //new Thread(Runnable target,String threadName) 构造方法 new Thread(()-> System.out.println(Thread.currentThread().getName()),"线程6") .start();//线程6
在main方法中可以获取主线程对象并设置:
Thread.currentThread().setName("我是主线程"); System.out.println(Thread.currentThread().getName());//我是主线程
休眠线程-sleep
Thread.sleep(毫秒) / Thread.sleep(毫秒,纳秒)
控制当前线程休眠若干毫秒- 1秒 = 1000毫秒
- 1秒 = 1000 * 1000 * 1000 纳秒 (100,000,000)
new Thread(()->{ for(int i = 0; i < 10 ;i++){ System.out.println(Thread.currentThread().getName()); try{ Thread.sleep(1000); //每个线程休眠1秒(1000毫秒) }catch(InterruptedException e){ e.printStackTrace(); } } },"测试线程1").start();
sleep方法不会释放锁,wait方法会释放锁
加入线程-join
join()
当前
线程暂停
,等待指定的线程执行结束
后,当前线程
才能再继续
。即把指定的线程插队处理。join(int ms)
可以等待指定的毫秒
后再继续。- join()方法会使
调用该方法的线程
处于运行状态
,让一开始所在的线程
处于无限阻塞状态
,直到调用了join方法的线程执行完毕,线程销毁为止。 - 下面这个例子中,t2线程处于了阻塞状态,直到t1线程的run()方法执行完,线程死亡状态,t2线程才可以运行。
public static void main(String[] args) {
//Java8中 匿名内部类调用外接方法和变量时,外接变量可以不用final修饰,但是其值还是不能改变
Thread t1 = new Thread() { //此时的t1在Java8之前必须用final修饰,是不可变的
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "aaa");
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 2) {
try {
//Java8中 匿名内部类调用外接方法和变量时,外接变量可以不用final修饰,但是其值还是不能改变
t1.join();//t1线程对象来插队了,t1执行完之后t2才能继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "bbb");
}
}
};
t1.start();
t2.start();
}
执行结果:
Thread-1bbb
Thread-1bbb
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-0aaa
Thread-1bbb
Thread-1bbb
Thread-1bbb
Thread-1bbb
Thread-1bbb
Thread-1bbb
Thread-1bbb
Thread-1bbb
结果显示:当t2线程执行两个后,t1使用join方法来插队,t1执行完之后,t2才继续执行完。
让出线程-yield
Thread.yield()
使该线程让出cpu
,给其他线程使用cpu执行- yield只会把时间片让给
同优先级的线程
- 使
CPU调度到其他线程
,让该线程从运行状态
回到可运行状态
设置线程优先级
thread.setPriority(int priority)
设置线程的优先级扫描二维码关注公众号,回复: 2516873 查看本文章- Thread类源码中有三种优先级:(1,5,10)
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
优先级值:默认为5,最大为10,最小为1;
不能超过1~10这个范围。
t1.setPriority(Thread.MIN_PRIORITY);//最小 t1.setPriority(Thread.MAX_PRIORITY);//最大
中断线程-Interrupt
- 中断可以理解为线程的一个
标志位
,它表示了一个运行中的线程是否
被其他线程进行了中断操作
。 - 其他线程可以调用该线程的
interrupt()
方法对其进行中断操作,同时该线程可以调用isInterrupted()
来感知其他线程对其是否进行了中断操作,从而做出相应。 - 也可以调用
Thread
中的静态方法interrupted()
对当前线程进行中断
操作,该方法会清除中断标志位
。 - 当抛出
InterruptedException
时,会清除中断标志位
,也就是说在调用isInterrupted会返回false。 - 如果线程调用了
wait()、sleep()、join()
方法而导致的阻塞
,可以中断线程
,并抛出InterruptedException
来唤醒
方法名 | 作用 | 备注 |
---|---|---|
public void interrupt() | 中断该线程对象 | 如果线程调用了 wait()、sleep()、join() 方法而导致的阻塞 ,可以中断线程 ,并抛出InterruptedException 来唤醒,并且中断标志位会被清除 |
public boolean isInterrupted() | 测试该线程对象是否被中断 | 中断标志位不会被清除 |
public static boolean interrupted() | 测试当前线程是否被中断 | 中断标志位会被清除 |
守护线程-Deamon
setDaemon(boolean on)
设置一个线程作为守护线程
。守护线程
为其他线程
的运行提供便利的服务
,最典型的应用便是GC线程 。该线程
不会单独执行
,当其他非守护
线程都执行结束
后,守护线程就没有可服务的对象了,就会自动退出
。public static void main(String[] args) { Thread t1 = new Thread(()->{ for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName()+"非守护线程"); } }); Thread t2 = new Thread(()->{ for (int i = 0; i < 30; i++) { System.out.println(Thread.currentThread().getName()+"守护线程"); } }); t2.setDaemon(true);//将t2设置成守护线程 t1.start(); t2.start(); }
- 第一次执行结果:
Thread-0非守护线程 Thread-0非守护线程 Thread-0非守护线程
说明:非守护线程直接执行完毕后,
守护线程还未开启执行
,就自动退出
了。第二次执行结果:
Thread-0非守护线程 Thread-1守护线程 Thread-1守护线程 Thread-0非守护线程 Thread-0非守护线程 Thread-1守护线程 Thread-1守护线程 Thread-1守护线程 Thread-1守护线程 Thread-1守护线程
- 根据结果发现,守护线程和非守护线程穿插执行,非守护线程执行完之后,守护线程继续执行了,
没有立即停止
,该现象为线程缓冲
,即守护线程正在执行,需要等到非守护线程的执行完毕信号
后,才能停止
下来,自动退出。
wait()和notify()/notifyAll()
Object类中的wait()、notify()、notifyAll()三个方法,每个对象都是有的,结合多线程后可以起到很大的效果。
wait()
wait()
方法作用是使当前执行的代码的线程进行等待
,当前线程会进入等待队列
中。wait()
代码处会停止执行
,直到接到通知
(notify())或者被中断
(Interrupt())。- 在
调用wait()之前
,线程必须获取该对象的锁
,因此wait()方法只能在同步代码中
调用执行。 - wait()方法可以使调用该线程的方法
释放共享资源的锁
,然后从运行状态退出,进入等待队列
,直到再次被唤醒。
notify()
唤醒等待的线程
,如果有多个线程在等待队列
中,那么会随机
挑选一个等待的线程,对其发出唤醒通知,并且使它等待获取该对象的对象锁
。等待获取对象锁
说明了即使收到了通知,wait 的线程也不会马上获取对象锁
,会在锁池
中进行等待notify方法的线程释放锁
才可以,获取了对象锁之后
才能从锁池中出去
进入可运行状态
。- 在调用notify()之前,和wait()一样,
必须在同步代码中调用
。因为有锁的操作。 - notify()不释放锁
notifyAll()
- notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,随机进入锁池,等待拿到对象锁,进入可运行状态。
如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException
☆ sleep()和wait()的区别
方法本质上:
- wait()方法时
Object
类中的实例方法
。 - 而sleep()方法时
Thread
类中的静态方法
- wait()方法时
使用环境上:
wait()
方法必须要在同步方法或同步代码块中
使用,因为它必须已经获得对象锁。- 而
sleep()
方法没有这个限制,它可以在任何地方
使用。
是否释放锁:
wait()
方法会释放
占有的对象锁,使该线程进入等待池
中。- 而
sleep()
方法不会释放
对象锁,只会让出CPU
。
使其继续执行方式上:
wait()
方法必须等待notify()/notifyAll()方法的唤醒通知
后,才会离开等待池并且如果再次获得CPU时间片
才会继续执行。而
sleep()
方法在休眠时间到达后
,如果再次获得CPU时间片
就会继续执行。
Java中用到的线程调度算法
- Java中用到的是抢占式的线程调度算法。一个线程用完CPU后,操作系统会根据线程优先级、线程饥饿程度等数据算出一个总的优先级并分配下一个时间片给某个线程。
Thread.sleep(0)的作用?
- 平衡CPU控制权的一种操作:
- 由于Java采用的是抢占式线程调度算法,因此可能就会
出现某条线程综合来看常常会获取到CPU的控制权
的情况,为了让某些优先级较低的线程也能获得到CPU控制权
,可以使用Thread.sleep(0)手动出发一次操作系统分配时间片的操作
,来平衡控制权。
- 由于Java采用的是抢占式线程调度算法,因此可能就会
线程六大状态
根据Thread类中定义的枚举类型State
值,可以看出有6中状态:
public enum State {
NEW,
RUNNABLE,
WAITING,
TIMED_WAITING,
TERMINATED;
}
新建
状态 NEW新建了Thread类对象,但是没有启动的线程。
new Thread()
可运行
状态 RUNNABLE线程对象新建后,调用
start()
方法即处于了RUNNABLE
状态。- 此状态线程可能在Java虚拟机中运行;
- 可能在等待CPU处理器分配资源。
- 一个线程只有获取到CPU的资源后,才可以运行其
run()
方法执行代码,否则就会处于排队等待
阻塞
状态 BLOCKED该线程正在等待同步锁来进入一个同步代码块中来使用CPU资源,此时该线程就处于阻塞状态。
等待
状态 WAITING线程调用以下方法时,会自己进入等待状态:
- 不带超时的
Object类
中的wait()
方法 - 不带超时的
Thread
类中的join()
方法 LockSupport
类中的park()
方法
一直等待,直到手动唤醒
- 不带超时的
超时等待
状态 TIMED_WAITING线程调用带有正的等待时间参数的下列各方法时,会处于超时等待状态:
Object
中的wait()
Thread
中的join()
Thread
中的sleep()
LockSupport
中的parkNanos()
LockSupport
中的parkUntil()
终止
状态 TERMINATED线程执行完毕
,或run()
方法全部执行结束
后,线程进入终止状态。- 终止状态的线程
不具备继续运行
的能力。
线程状态图
- 锁池队列:当资源被一个线程访问时,上锁后,其他线程就会进入锁池队列,当锁释放后,其他线程获得了锁,就会变成可运行状态。
《Thinking in Java》
中线程被阻塞的五种可能原因:
- 线程调用
sleep(ms)
,使线程睡眠,规定时间内,该线程不会运行。 - 使用
suspend()
暂停了线程的执行,除非收到resume()
消息,否则不会进入可运行状态 - 线程正在等待一些IO操作完成
- 线程试图调用另一个对象的同步方法,但那个对象处于锁状态,暂时无法使用
- 调用
wait()
暂停了线程的执行,进入了等待队列。
- 线程调用
怎么唤醒一个阻塞的线程
- 如果线程调用了
wait()、sleep()、join()
方法而导致的阻塞,可以中断线程,并抛出InterruptedException
来唤醒 - 如果该线程遇到了IO阻塞,只能等系统IO操作结束后,才能唤醒,Java代码无能为力,无法直接接触到底层操作系统的调度。
怎么检测一个线程是否持有对象监视器
- Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true
public static boolean holdsLock(Object obj)