Java多线程

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用匿名内部类的形式
  4. 使用lambda表达式
  5. 使用Callable和Future创建
  6. 使用线程池
  7. Spring的@Async注解

使用Callable和Future创建

futureTask的get方法之所以会阻塞是因为实现方法中使用了LockSupport.park()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ThreadCallable implements Callable<Integer> {
  @Override
  public Integer call() throws Exception {
    System.out.println(Thread.currentThread().getName() + " 开始执行");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "执行结束");
    return 1;
  }
}


public class Test01 {

  public static void main(String[] args) throws Exception {
    ThreadCallable threadCall = new ThreadCallable();
    FutureTask<Integer> futureTask = new FutureTask<>(threadCall);
    new Thread(futureTask).start();
    Integer result = futureTask.get();
    System.out.println(Thread.currentThread().getName() + "," + result);
  }
}

线程安全

synchronized

用法:

  1. 修饰代码块 :指定加锁对象,进入同步代码块前要获得给定对象的锁
  2. 修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  3. 修饰静态方法:作用于当前类对象(类.class)加锁,进入同步代码块的时候需要获得当前类对象的锁

线程同步

线程如何保证同步,即如何保证线程安全性问题

  1. 使用synchronized锁,注意锁升级过程
  2. 使用Lock锁,锁升级
  3. 使用ThreadLocal,需要注意内存泄漏的问题
  4. 使用原子类,CAS非阻塞

线程通讯

等待/通知机制

  1. wait():线程阻塞并且释放锁
  2. notify():通知一个在对象上等待的线程,使其返回main()方法继续执行。返回的前提是改线程已获得了对象的锁。
  3. notifyAll() :通知所有等待在该对象的线程

需要结合synchronoized关键字来使用:两个线程之间的通信,对于同一个对象来说,一个线程调用对象的wait()方法,一个线程调用对象的notify()方法,这个对象本身就需要同步。所以在调用wait()notify()方法之前,需要使用synchronized关键字同步对象。

join()

join底层原理是基于wait()方法封装。

唤醒?

线程的状态

image-20220508170932567

  • 初始化状态
  • 就绪状态
  • 运行状态
  • 死亡状态
  • 阻塞状态
  • 超时等待
  • 等待状态

守护线程和用户线程

Java中的线程分为两种:守护线程和用户线程

创建出来的线程,默认为用户线程,通过Thread.setDaemon(true)来将线程设置为守护线程。

守护线程依赖于用户线程,用户线程退出了,其中的守护线程也就退出了,典型的额守护线程—垃圾回收线程。

用户线程是独立存在的,不会应为其他用户线程的退出而退出。

安全的停止一个线程

  1. stop():终止线程,并且清楚监控器锁的信息。
  2. destory():
  3. interrupt():打断正在运行或者正在阻塞的线程。
    1. 如果目标线程在调用Object classwait()wait(long)wait(long, int)方法、join()join(long, int)或者sleep(long, int)方法时被阻塞,那么interrupt会生效,改线程中断状态将会被清除,抛出InterruptedException异常。
    2. 如果目标线程是被I/O或者NIO中的Channel所阻塞,同样,I/O操作会被终端或者返回特殊的异常值,达到终止线程的目的。
    3. 如果以上条件都不满足,则会设置此线程为中断状态。
  4. 标志位

Lock锁

使用ReentrantLock实现同步

  • lock()方法上锁
  • unlock()方法释放锁

使用Condition实现等待/通知 类似于wait()notify()notifyAll()

Condition

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Test02 {

  private Lock lock = new ReentrantLock();

  private Condition condition = lock.newCondition();

  private void signal(){
    try {
      lock.lock();
      // 唤醒线程
      condition.signal();
    } catch(Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }

  }

  public static void main(String[] args) throws Exception {
    Test02 test02 = new Test02();
    test02.cal();

    Thread.sleep(3000);
    test02.signal();
  }

  public void cal(){
    new Thread(() -> {
      try {
        lock.lock();
        System.out.println("Thread is waiting...");
        // 阻塞线程
        condition.await();
        System.out.println("Thread is running...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      } finally {
        lock.unlock();
      }
    }).start();
  }
}

yield()

该方法会是线程主动放弃CPU的执行权

  1. 多线程yield()会让线程从云心状态进入到就绪状态,让后调度执行的其他线程来竞争CPU。
  2. 具体的实现依赖于底层操作系统的任务调度器

优先级

  1. 在Java语言中,每个线程都有优先级,当线程调控器有机会选择新的线程时,线程的优先级越高,越有可能被执行。优先级可以设置1~10。数字越大代表优先级越高。(Oracle为Linux提供的Java虚拟机中,线程的优先级被忽略,即所有线程具有相同的优先级)。所以不能过度的依赖优先级
  2. 线程的优先级用数字来表示。默认范围是1~10.即Thread.MIN_PRIORITYThread.MAX_PRIORITY
  3. 如果CPU非常繁忙,优先级越高的线程获得更多的时间片,但是CPU空闲时,设置优先级几乎没有任何作用。

问题

Join()/wait()和sleep()的区别

  • sleep(long)方法在睡眠时不释放对象锁。

  • join(long)方法先执行另外一个线程,在等待的过程中释放锁,底层是基于wait()方法实现的。

  • wait(long)方法在等待的过程中会释放锁。

wait()/notify()在Object类中的原因

使用wait()方法的时候需要结合synchronized关键字,synchronized这把锁可以是任意对象,所以任意对象都可以调用wait()和notify()。

Callable和FutureTask

LockSupport


相关内容

0%