多线程

进程

  • 正在运行的程序就是一个独立的进程
  • 线程属于进程,一个进程中可以同时运行很多个线程
  • 进程中的线程是并发和并行执行的

线程

  • 线程(Thread) 是一个程序内部的一条执行流程
  • 程序中如果只有一条执行流程,那这个程序就是单线程的程序。

生命周期

  • New 新建
  • Runnable 运行
  • Teminated 终止
  • Blocked 锁阻塞
  • Waiting 无限等待
  • Timed Waiting 计时等待 sleep 不会释放锁 wait会释放

多线程

  • 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)
  • main 方法是一个主线程
  • 启动线程必须调用start方法而不是run方法,run方法不会被认为是一个线程。
  • 不要把主线程任务放在启动子线程之前,主线程任务在前面的话会先执行完主线程任务。

创建线程

  1. 通过继承Thread类

    • 优点:编写简单
    • 缺点:不能继承其他类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 //1、通过继承Thread类

public class MyThread extends Thread{

//必须重写run方法
@Override
public void run(){
//描述线程执行任务
}
}

//启动线程
Thread t = new Mythread();
t.start();

//join方法 让当前调用这个方法的线程先执行完
t.join();
  1. 实现Runnable接口

    • 优点:可以继承其他类和其他接口

    • 缺点:多一个Runnable对象

    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
    public class MyRunnable implements Runnable{
    //必须重写run方法
    @Override
    public void run(){
    //描述线程执行任务
    }
    }

    //启动线程
    //创建任务
    Runnable target = new MyRunable();
    //把任务交给线程对象
    new Thread(target).start();

    //匿名内部类
    Runnable target = new Runnalbe(){
    @Override
    public void run(){
    //描述线程执行任务
    }
    };
    new Thread(target).start();

    //简化
    new Thread(() ->{
    //描述线程执行任务

    }).start();
  2. 实现Callable接口

    • 优点:可以返回线程执行完毕的结果
    • 缺点:编码复杂
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    public class MyCallable implements Callable<String>{
    int n;
    Mycallable(int n){
    this.n = n;
    }
    //必须重写call方法
    @Override
    public String call() throws Exception{
    //描述线程执行任务

    return n + "";
    }
    }

    Callable<String> call = new MyCallable(100);

    //FutureTask 实现了Runnable 对象
    FutureTask<String> f1 = new FutureTask<>(call);

    new Thread(f1).start();

    //得到结果,会等待上面线程执行完毕
    String rs = f1.get();

线程安全问题

  • 多个线程,同时操作同一个共享资源,出现的业务安全问题

线程同步

  • 解决线程安全问题的方案

解决方案

  • 加锁:每次只允许一个线程加锁

    • 同步代码块

      • 作用:把访问共享资源的核心代码上锁,以此来保证线程安全
      • 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
      • 注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
      1
      2
      3
      synchronized(同步锁(一般为共享资源)){

      }
    • 同步方法

      • 作用:把访问共享资源的核心方法给上锁,以此保护线程安全
      • 有隐式锁,锁整个方法
      • 对于实例方法一般为this
      • 对于静态方法为 类名.class
      • 锁的范围越小,性能更好
      1
      2
      3
      public synchronized void test(){

      }
    • Lock 锁

      • Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
      • Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      private final Lock lk = new ReentrantLock();

      //加锁
      lk.lock();
      try{
      //代码
      }finally{
      //解锁
      lk.unlock();
      }



线程通信

  • 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

线程池

  • 一个可以复用线程的技术
  • 工作原理
    • 工作线程
    • 任务队列
      • 任务接口 - Runnable Callable
创建线程池

线程池接口 ExecutorService

线程池实现类 ThreadPoolExecutor

  • 方式一:使用ThreadPoolExecutor自创建一个线程池对象

    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
    // 核心线程,最大线程,临时线程持续时间,时间单位,任务队列,线程创建工厂,任务拒绝策略
    ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExector.AbortPolicy());

    //执行Runnable 任务 自动创建一个新线程,自动处理,自动执行
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    //复用前面的线程
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    //任务队列满了,提供临时线程
    pool.execute(Runnable command);
    pool.execute(Runnable command);
    //都满了开始拒绝任务策略
    pool.execute(Runnable command);


    //执行Callable 任务
    Future<T> res = pool.submit(Callable<T> task)

    //等待所有任务执行完毕,然后关闭线程池
    pool.shutdown();
    //立即关闭
    pool.shutdownNow();


    • 拒绝策略
      • AbortPolicy 拒绝被抛出异常
      • DiscardPolicy 拒绝不抛出异常
      • DiscardOldestPolicy 抛弃队列中等待最久的任务
      • CallerRunsPolicy 主线程绕过线程池自己来做
  • 方式二:使用Executors 调用方法返回不同特点的线程池对象

    • newFixedThreadPool(3) 创建固定线程数的线程池
    • newSingleThreadPool 创建一个线程数的线程池
    • newCachedThreadPool 线程数量随着任务增加,空闲了60s后线程会删除
    • newScheduledThreadPool() 创建固定线程数的定时器线程池

悲观锁

  • 一上来就加锁,每次只能一个线程进入,访问完毕后再解锁,线程安全,性能较差Ω

乐观锁

  • 一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
  • 改变之后比较,比较之后发现原数据没变就把改变的数据放回。
  • 整数修改的乐观锁,原子类实现