16-多线程

nobility 发布于 2021-03-27 377 次阅读


多线程

多线程类体系结构和声明周期

多线程类体系结构

实现多线程的方法

启动线程必须通过Thread类的start()方法,因为线程的运行需要计算机操作系统的支持,查看该方法的源码发现,若重复启动线程则会抛出IllegalThreadStateException异常,并进行了系统调用native void start0()方法,带有native修饰的方法表示操作系统的系统函数

继承Thread类

构造方法
方法名 描述
Thread() 创建默认命名线程对象
Thread(String name) 创建指定名的线程对象
Thread(Runnable target) 创建基于Runnable接口实现类的线程对象,使用默认命名
Thread(Runnable target, String name) 创建基于Runnable接口实现类的线程对象,使用指定命名

自己启动线程只能启动一次(若启动多次会抛出异常),利用另一个线程对象启动可以对同一个对象启动多次(没必要,浪费资源)

实现Runnable接口

重写run()方法,使用Thread来启动线程

实现Callable接口

重写call()方法,此方法有返回值,使用FutureTask类对Callable实现类进行包装,再使用Thread来启动线程,使用Futuretask的get()方法来获取返回值,获取返回值时调用线程会处于阻塞状态

要注意的是,使用FutureTask类进行包装后就无法对同一给FutureTask对象启动多次(若启动多次只有随机的一个线程执行),而未使用FutureTask类进行包装的(Runnable实现类和Thread子类)可以用另一个线程对象启动多次

线程控制

线程控制由Thread类的方法来实现

线程获取与命名

方法名 描述
String getName() 获取线程名称
void setName(String name) 设置或修改线程名称
static native Thread currentThread() 获取当前线程对象

线程休眠

方法名 描述
static native void sleep(long millis) 使线程阻塞,单位毫秒
static void sleep(long millis, int nanos) 使线程阻塞,单位毫秒.纳秒

线程中断

方法名 描述
boolean isInterrupted() 判断线程中断终态
void interrupt() 中断线程
Thread thread = new Thread(()->{
  try {
    Thread.sleep(20000);
  } catch (InterruptedException e) {
    System.out.println("线程中断");
//        e.printStackTrace();
  }
});
thread.start();
System.out.println(thread.getName()+":"+thread.isInterrupted());
Thread.sleep(500);
thread.interrupt();
System.out.println(thread.getName()+":"+thread.isInterrupted());
//有时候是false有时候是true,不知道为啥
System.out.println(Thread.currentThread().getName()+"执行完毕");

线程强制执行

方法名 描述
void join() 让出当前线程资源,交由该线程执行到结束后,当前线程再执行
void join(final long millis) 强制执行若干毫秒
void join(long millis, int nanos) 强制执行若干毫秒.纳秒
Thread thread0 = new Thread(()->{
  for (int i = 0; i < 50; i++) {
    try {
      Thread.sleep(50);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+":"+i);
  }
});
Thread thread1 = new Thread(()->{
  for (int i = 0; i < 50; i++) {
    try {
      Thread.sleep(50);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+":"+i);
  }
});
thread0.start();
thread1.start();
for (int i = 0; i < 50; i++) {
  if (i == 10){
    thread0.join();
    //此时主线程让出资源,不在执行,thread0和thread1交替执行
    //而并不是只有thread0执行
  }
  Thread.sleep(50);
  System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println(Thread.currentThread().getName()+"执行完毕");

线程礼让

方法名 描述
static native void yield() 当前线程礼让一次,随后继续按照抢占式继续执行

线程优先级

线程优先级必须在MAX_PRIORITY(10)和MIN_PRIORITY(1)之间(若不再则会抛出IllegalArgumentException非法参数异常),默认优先级是NORM_PRIORITY(5)

优先级高只表示抢到cup的可能性高,并不表示一定先执行

方法名 描述
void setPriority(int newPriority) 设置优先级
int getPriority() 获取优先级

守护线程

所有非守护线程结束,守护线程自动结束

方法名 描述
void setDaemon(boolean on) 设置守护线程

线程同步

synchronized关键字

同步代码块
synchronized(锁对象){
  多条控制语句
}
//多条线程只有看到是同一把锁才能使得代码块中的代码同步执行
//同一把锁指的是同一个对象,即地址相同
同步方法
  • 使用synchronized修饰方法即可
  • 普通同步方法的锁对象是当全对象this
  • 静态同步方法的锁对象是该类的反射对象类名.class

Lock锁

Lock是个接口,实现类是不同形式的锁,与synchronized相同的机制的锁就是其子类ReentrantLock(可重入锁),意思就是当一个同步方法调用另一个同步方法时不用再去申请加锁

此方法加锁一般会用try...finally块来包裹,以防止出错而无法解锁

方法名 描述
void lock() 加锁
void unlock() 解锁

volatile修饰符

  • 只能用于成员变量上
  • 传统线程操作数据是将主内存的数据copy到此线程工作内存中成为副本,对副本进行一系列操作后写回主内存。而volatile修饰的变量则是此线程直接操作主内存的数据,所以内存变量访问更加快

线程等待与唤醒机制

此些方法都在Object类中定义

方法名 描述
void wait() 线程等待
native void wait(long timeoutMillis) 线程等待,且有超时时间
native void notify() 随机唤醒一个等待线程
native void notifyAll() 唤醒全部等待线程,那个优先级高那个先执行
public class Main{
  public static void main(String[] args) {
   Good good = new Good();	//相当于一个仓库,生产者和消费者共用统一个仓库
   Producer producer = new Producer(good);
   Consumer consumer = new Consumer(good);
   new Thread(producer).start();
   new Thread(consumer).start();
  }
}
class Good {
  private String title;
  private String info;
  private boolean flag = true;//true生产,false消费
  public synchronized void set(String title, String info) {
    //问题:数据错位,就是没有生产完呢就消费了,消费的是上次留下的
    //加上同步可解决数据错位
    if (this.flag == false){
      //问题:生产好几个才消费一个
      //加上标志位和等待唤醒机制可解决生产一个消费一个
      try {
        wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    this.title = title;
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    this.info = info;
    this.flag = false;
    notify();
  }
  public synchronized void get() {
    if (this.flag == true){
      try {
        wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    try {
      Thread.sleep(50);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(this.title +":"+this.info);
    this.flag = true;
    notify();
  }
}
class Producer implements Runnable{
  private Good good;
  public Producer(Good good) {
    this.good = good;
  }
  @Override
  public void run() {
    for (int i = 0; i < 50; i++) {
      if (i % 2 == 0){
        this.good.set("A","A");
      }else{
        this.good.set("B","B");
      }
    }
  }
}
class Consumer implements Runnable{
  private Good good;
  public Consumer(Good good) {
    this.good = good;
  }
  @Override
  public void run() {
    for (int i = 0; i < 50; i++) {
     this.good.get();
    }
  }
}

ThreadLocal

解决多个线程内部使用同一个线程外部对象,修改值覆盖问题,ThreadLocal存储的数据结构存储当前线程标记和要存储的内容,所以对不同的线程都有与之对应的一个内容,不会被覆盖掉(效果相当于在线程内部new了一个公共用的那个对象,但是实际上不是,这样更加节省堆空间内存)

方法名 描述
void set(T value) 设置或修改内容
T get() 获取内容
void remove() 删除内容
T initialValue() 设置初始值,也就是当前值为null的默认值,用于方法重写
此作者没有提供个人介绍
最后更新于 2021-03-27