Java多线程基础-线程的实例方法

线程相关实例方法

获取线程ID- getId

  • 在一个Java应用程序中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程就会自增一次,从0开始,并且赋值给线程的tid属性。
  • 用户只能获取ID,不能执行一个线程的ID,这是Thread类内部自己完成的。

获取和设置线程的名字

  • 获取线程名

    • 通过getName()方法获取线程对象名

      1
      2
      3
      4
      5
      6
      new Thread(){
      @Override
      public void run() {
      System.out.println(this.getName());//Thread-0
      }
      }.start();
  • 设置线程名

    • 通过构造函数传入String类型名

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      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();
      1
      2
      3
      4
      5
      //Lambda表达式的Runnable方式,Thread的构造函数
      Thread t2 = new Thread(() ->
      System.out.println("线程5的执行方法体"),"线程5");
      t2.start();
      System.out.println(t2.getName());//线程5
  • 通过setName(String name)方法设置

    1
    2
    3
    4
    5
    6
    7
    new Thread(){
    @Override
    public void run() {
    this.setName("线程3");
    System.out.println(this.getName());//线程3
    }
    }.start();
    1
    2
    3
    4
    5
    6
    7
    8
    Thread t1 = new Thread() {
    @Override
    public void run() {
    System.out.println(this.getName());//线程4
    }
    };
    t1.setName("线程4");
    t1.start();
1
2
3
4
5
6
7
8
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() 静态方法,获取当前执行线程, 主线程也可以获取

    1
    2
    3
    4
    //Runnable接口方式
    //new Thread(Runnable target,String threadName) 构造方法
    new Thread(()-> System.out.println(Thread.currentThread().getName()),"线程6")
    .start();//线程6

    在main方法中可以获取主线程对象并设置:

    1
    2
    Thread.currentThread().setName("我是主线程");
    System.out.println(Thread.currentThread().getName());//我是主线程

休眠线程-sleep

  • Thread.sleep(毫秒) / Thread.sleep(毫秒,纳秒) 控制当前线程休眠若干毫秒

    • 1秒 = 1000毫秒
    • 1秒 = 1000 1000 1000 纳秒 (100,000,000)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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线程才可以运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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) 设置线程的优先级

    • Thread类源码中有三种优先级:(1,5,10)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      /**
      * 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这个范围。

    1
    2
    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线程 。

  • 该线程不会单独执行,当其他非守护线程都执行结束后,守护线程就没有可服务的对象了,就会自动退出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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();

    }
    • 第一次执行结果:
    1
    2
    3
    Thread-0非守护线程
    Thread-0非守护线程
    Thread-0非守护线程
    • 说明:非守护线程直接执行完毕后,守护线程还未开启执行,就自动退出了。

    • 第二次执行结果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      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()的区别

  1. 方法本质上:

    • wait()方法时Object类中的实例方法
    • 而sleep()方法时Thread类中的静态方法
  2. 使用环境上:

    • wait()方法必须要在同步方法或同步代码块中使用,因为它必须已经获得对象锁。
    • sleep()方法没有这个限制,它可以在任何地方使用。
  3. 是否释放锁:

    • wait()方法会释放占有的对象锁,使该线程进入等待池中。
    • sleep()方法不会释放对象锁,只会让出CPU
  4. 使其继续执行方式上:

    • wait()方法必须等待 notify()/notifyAll()方法的唤醒通知后,才会离开等待池并且如果再次获得CPU时间片才会继续执行。

    • sleep()方法在休眠时间到达后,如果再次获得CPU时间片就会继续执行。

Java中用到的线程调度算法

  • Java中用到的是抢占式的线程调度算法。一个线程用完CPU后,操作系统会根据线程优先级、线程饥饿程度等数据算出一个总的优先级并分配下一个时间片给某个线程。

Thread.sleep(0)的作用?

  • 平衡CPU控制权的一种操作:
    • 由于Java采用的是抢占式线程调度算法,因此可能就会出现某条线程综合来看常常会获取到CPU的控制权的情况,为了让某些优先级较低的线程也能获得到CPU控制权,可以使用Thread.sleep(0)手动出发一次操作系统分配时间片的操作,来平衡控制权。