多线程

发布时间 2023-08-21 13:14:16作者: lwx_R

1.线程

1.1 创建

  • 继承 Thread类
public class MyThread extends Thread{
    /**
     * Thread 类本质上是实现了Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方
     * 法就是通过Thread 类的start()实例方法。start()方法是一个native 方法,它将启动一个新线
     * 程,并执行run()方法。
     */
    public void run(){
        System.out.println("My Thread");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • 实现Runnable接口
public class MyThread1 implements Runnable{

    @Override
    public void run() {
        System.out.println("My Thread 1");
    }

    public static void main(String[] args) {
        MyThread1 thread1 = new MyThread1();
        Thread thread = new Thread(thread1);
        thread.start();
    }
  • 有返回值线程
public class MyThread2 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return 1;
    }

    /**
     * 有返回值的任务必须实现Callable 接口,类似的,无返回值的任务必须Runnable 接口。执行
     * Callable 任务后,可以获取一个Future 的对象,在该对象上调用get 就可以获取到Callable 任务
     * 返回的Object 了,再结合线程池接口ExecutorService 就可以实现传说中有返回结果的多线程了。
     * @param args
     */
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // 创建多个有返回值的任务
        List<Future> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Callable c = new MyThread2();
            // 执行任务并获取Future 对象
            Future f = pool.submit(c);
            list.add(f);
        }
        // 关闭线程池
        pool.shutdown();
        // 获取所有并发任务的运行结果
        for (Future f : list) {
        // 从Future 对象上获取任务的返回值,并输出到控制台
            try {
                System.out.println("res:" + f.get().toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
}
  • 基于线程池
/**
* 线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销
* 毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
*/
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
  threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
      @Override
      public void run() {
          System.out.println(Thread.currentThread().getName() + " is running ..");
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  });
}

1.2 线程池

顶级接口Executor 执行线程的工具 线程池接口ExectorService

  • newCachedThreadPool

1.3 生命周期

  • 新建状态:new线程后
  • 就绪:调用start
  • 运行:获得cpu,执行run
  • 阻塞:1.等待阻塞:wait方法,进入等待队列。 2.同步阻塞:获取对象同步锁时,其被占用,则放入锁池。 3.其他阻塞:现场sleep或join或发出IO请求,设置为阻塞,请求完转入可运行状态
  • 死亡:1.正常死亡:run/call完成 2.异常结束:线程抛出未捕获的异常。 3.调用stop:容易死锁。

1.3.1 终止线程方式

  • 正常结束
  • 使用退出标志
public class ThreadSafe extends Thread{
    // volatile使exit同步
    private volatile boolean exit = false;

    public void run(){
        while (!exit){
            
        }
    }
}
  • Interrupt方法
    1.线程阻塞状态:调用interrupt,抛出InterruptException 异常,break跳出循环,结束线程
    2.未阻塞状态:isInterrupted()判断线程的中断标志来退出
//2.isInterrupted()方法
while (!isInterrupted()) { //非阻塞过程中通过判断中断标志来退出
    try {
        Thread.sleep(5 * 1000);//阻塞过程捕获中断异常来退出
    } catch (InterruptedException e) {
        e.printStackTrace();
        break;//捕获到异常之后,执行break 跳出循环
    }
}
  • stop方法:会释放子线程所持有的所有锁,线程不安全

1.3.2 sleep和wait

  • sleep:属于Thread

1.3.3 start和run

1.4 Java后台线程

1.5 锁

1.5.1 乐观锁

拿数据不上锁,更新时比较版本号,加锁操作
CAS操作实现,比价当前值和传入值是否一样,一样更新

1.5.2 悲观锁

拿数据上锁,别的线程会block直达拿到锁
AQS框架下,先尝试CAS乐观锁获取锁,拿不到转化为悲观锁

1.5.2.1 synchronized 同步锁

可以把任意一个非NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。

  • 作用范围

1.5.2.2 ReentrantLock

1.5.3 自旋锁

如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

1.5.4 信号量