相关概念

  • 程序(program):一段静态的代码,一组指令的有序集合。存储在硬盘上
  • 进程(process):进程是程序的动态的执行,进程是最小的独立的内存空间
  • 线程(Thread):是在进程中更小的运行单位,每个进程必须要有一个线程,线程是调用CPU的最小的独立单位。多个线程会共享同一个进程中的内存
  • 协程或者纤程是比线程更小的运行单位

    线程的基本使用

    第一种创建的方法

  • 创建一个继承了Thread类的类,在run方法中实现子线程的代码

    1. public class MyThread extends Thread{
    2. //当前线程的运行代码
    3. @Override
    4. public void run() {
    5. String name = Thread.currentThread().getName();
    6. System.out.println("子线程"+name+"运行的代码");
    7. }
    8. }
  • 在主线程中,创建子线程,并且通过start进行启动

    1. public static void main(String[] args) {
    2. //获取当前线程的名称
    3. String name = Thread.currentThread().getName();
    4. System.out.println("主线程"+name+"运行的代码:你好,同学");
    5. //创建子线程对象
    6. MyThread thread = new MyThread();
    7. //启动子线程
    8. thread.start();
    9. //thread.run();//主线程调用的线程
    10. //
    11. }

    案例1:多线程抢票

  • 创建抢票线程

    1. public class TicketThread extends Thread {
    2. static int ticketNum = 10;
    3. @Override
    4. public void run() {
    5. while(ticketNum>0){
    6. String name = Thread.currentThread().getName();
    7. System.out.println(name+"抢到第"+ticketNum--+"张票");
    8. }
    9. }
    10. }
  • 启动线程进行抢票(创建多个主线程对象,并启动子线程)

    1. public static void main(String[] args) {
    2. //创建线程
    3. TicketThread thread1 = new TicketThread();
    4. TicketThread thread2 = new TicketThread();
    5. TicketThread thread3 = new TicketThread();
    6. //启动线程
    7. thread1.start();
    8. thread2.start();
    9. thread3.start();
    10. }

    多线程的第二种创建方式

  • 创建一个实现了runnable接口的类

    1. public class MyRunnable implements Runnable {
    2. @Override
    3. public void run() {
    4. String name = Thread.currentThread().getName();
    5. System.out.println(name+"执行的代码");
    6. }
    7. }
  • 将runnable创建出来之后,必须放到一个线程中才可以运行

    1. public static void main(String[] args) {
    2. //Runable可以理解成线程的一部分,必须要以来一个线程才才运
    3. MyRunnable myRunnable = new MyRunnable();
    4. //创建一个线程
    5. Thread thread = new Thread(myRunnable);
    6. thread.start();
    7. System.out.println(Thread.currentThread().getName()+"运行代码");
    8. }
  • 线程池就是一个容器

    • 可以对线程的创建,调用,销毁进行维护
    • 调用方只需要是实现runnable接口任务,将任务发送给线程池,线程池会自行调用线程完成任务

      1. MyRunnable r1 = new MyRunnable("任务1");
      2. MyRunnable r2 = new MyRunnable("任务2");
      3. MyRunnable r3 = new MyRunnable("任务3");
      4. MyRunnable r4 = new MyRunnable("任务4");
      5. MyRunnable r5 = new MyRunnable("任务5");
      6. //创建线程池,设置固定的线程数量
      7. ExecutorService service = Executors.newFixedThreadPool(2);
      8. //通过线程池运行任务
      9. service.execute(r1);
      10. service.execute(r2);
      11. service.execute(r3);
      12. service.execute(r4);
      13. service.execute(r5);
      14. //关闭线程池
      15. service.shutdown();

      ExecutorService service = Executors.newFixedThreadPool(2);创建线程池,2代表线程的数量
      service(线程池名称).execute(r1);通过线程池运行任务
      service.shutdown();关闭线程池

      案例2:线程池抢票

  • 创建抢票的任务

    1. public class TicketRunnable implements Runnable{
    2. public static int ticketNum = 100;
    3. String name;
    4. public TicketRunnable(String name) {
    5. this.name = name;
    6. }
    7. @Override
    8. public void run() {
    9. //每次被调用只抢一张票
    10. System.out.println(
    11. Thread.currentThread().getName()+"调用"
    12. +this.name+"抢到了第"+ticketNum--+"张票"
    13. );
    14. }
    15. }
  • 使用线程池创建3个线程,10个任务,抢100张票

    1. public class TestThread005 {
    2. public static void main(String[] args) throws InterruptedException {
    3. List<Runnable> list = new ArrayList<>();
    4. for (int i=1;i<=10;i++){
    5. list.add(new TicketRunnable("任务"+i));
    6. }
    7. //创建线程池
    8. ExecutorService service = Executors.newFixedThreadPool(3);
    9. Random random = new Random();
    10. while(TicketRunnable.ticketNum>0){
    11. int index = random.nextInt(10);
    12. service.execute(list.get(index));
    13. Thread.sleep(1);
    14. }
    15. service.shutdown();
    16. }
    17. }

    第三种创建方式

  • 无论是用Thread还是Runable都是实现了run方法,但是run方法有一下问题

      • 没有返回值
      • 不能抛出异常
  • 创建一个实现了callable接口的类

    1. public class MyCallable implements Callable<Integer> {
    2. int start;
    3. int end;
    4. public MyCallable(int start, int end) {
    5. this.start = start;
    6. this.end = end;
    7. }
    8. @Override
    9. public Integer call() throws Exception {
    10. System.out.println("子线程开始计算");
    11. int result =0;
    12. for(int i=start;i<=end;i++){
    13. result+=i;
    14. }
    15. return result;
    16. }
    17. }
  • 将callable绑定到futureTask中,在将futureTask绑定到线程中进行运行

    1. //创建call对象
    2. MyCallable myCallable = new MyCallable(1,50);
    3. MyCallable myCallable2 = new MyCallable(51,100);
    4. //将call绑定到FutureTask
    5. FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
    6. FutureTask<Integer> futureTask1 = new FutureTask<>(myCallable2);
    7. //再将FutureTask绑定到线程上才可以运行
    8. Thread thread = new Thread(futureTask);
    9. Thread thread1 = new Thread(futureTask1);
    10. //运行线程
    11. thread.start();
    12. thread1.start();
    13. System.out.println("主线程运行了");
    14. //调用futureTask获取返回结果
    15. int res1 = futureTask.get();//保证myCallable执行完毕
    16. int res2 = futureTask1.get();//保证myCallable2执行完毕
    17. //能够确保子线程运行完毕之后再继续运行
    18. System.out.println("主线还在运行过程中");
    19. System.out.println("计算的结果是"+(res1+res2));

    线程的常用方法

  • getName():可以获取线程的名称

  • sleep(); 可以让线程进入到休眠状态,并且会阻塞当前线程
  • join():让父线程等待子线程结束之后才能继续运行,即在一个线程A中调用另一个线程B的join方法,那么当前线程A停止,等待另一个线程B执行完毕之后再执行

    1. String name = Thread.currentThread().getName();
    2. System.out.println(name+"1");
    3. System.out.println(name+"2");
    4. Thread child = new Thread(){
    5. @Override
    6. public void run() {
    7. String name = Thread.currentThread().getName();
    8. System.out.println(name+"1");
    9. System.out.println(name+"2");
    10. System.out.println(name+"3");
    11. }
    12. };
    13. child.start();
    14. System.out.println(name+"3");
    15. System.out.println(name+"4");
    16. System.out.println(name+"5");
    17. child.join();
    18. int i =100;
    19. while(i>0){
    20. System.out.println(name+i--);
    21. }
  • setDaemon,将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要再继续执行了,必须要是start调用之前调用

    1. Thread thread = new Thread(){
    2. @Override
    3. public void run() {
    4. for (int i=0;i<100;i++){
    5. System.out.println("子线程"+i);
    6. }
    7. }
    8. };
    9. thread.setDaemon(true);//父类线程关闭之后,子线程马上关闭
    10. thread.start();
  • setPriority:修改获取线程资源的优先级

    1. Thread thread1 = new Thread(){
    2. @Override
    3. public void run() {
    4. for (int i=0;i<100;i++){
    5. System.out.println("*子线程1*"+i);
    6. }
    7. }
    8. };
    9. Thread thread2 = new Thread(){
    10. @Override
    11. public void run() {
    12. for (int i=0;i<100;i++){
    13. System.out.println("*子线程2*"+i);
    14. }
    15. }
    16. };
    17. thread2.setPriority(Thread.MAX_PRIORITY);
    18. thread1.setPriority(Thread.MIN_PRIORITY);
    19. thread1.start();
    20. thread2.start();
  • Thread.yield();退让,会先去判断是否有和当前线程相同优先级的线程,如果没有,则自己继续执行,如果有,则将CPU资源让给它,然后进入到就绪状态。

    ThreadLocal

  • simpleDateFormat是被多线程使用的时候,会出现线程并发的问题、、

    • 实现一个工具

      1. public class DateUtil {
      2. private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      3. public static String formatDate(Date date)throws ParseException {
      4. return sdf.format(date);
      5. }
      6. public static Date parse(String strDate) throws ParseException{
      7. return sdf.parse(strDate);
      8. }
      9. }
    • 并发的场景

      1. public static void main(String[] args) throws InterruptedException {
      2. //创建一个线程池
      3. ExecutorService executorService = Executors.newFixedThreadPool(5);
      4. //执行15个任务
      5. for (int i = 0; i < 5; i++) {
      6. executorService.execute(() -> {
      7. try {
      8. System.out.println(DateUtil.parse("2022-08-03 15:00:20"));
      9. } catch (ParseException e) {
      10. e.printStackTrace();
      11. }
      12. });
      13. }
      14. executorService.shutdown();
      15. }
    • 方法1:可以使用synchronized解决,影响性能

    • 方式2:创建多个对象,浪费内存
    • 方式3:通过ThreadLocal为每个线程创建一个独立的对象副本

      1. private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      2. //使用ThreadLocal
      3. static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
      4. public static String formatDate(Date date)throws ParseException {
      5. //从threadlocal中获取副本对象
      6. SimpleDateFormat simpleDateFormat = threadLocal.get();
      7. if(simpleDateFormat == null){
      8. //添加每个线程的副本对象
      9. threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
      10. simpleDateFormat = threadLocal.get();
      11. }
      12. return simpleDateFormat.format(date);
      13. }
      14. public static Date parse(String strDate) throws ParseException{
      15. SimpleDateFormat simpleDateFormat = threadLocal.get();
      16. if(simpleDateFormat == null){
      17. //添加每个线程的副本对象
      18. threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
      19. simpleDateFormat = threadLocal.get();
      20. }
      21. return simpleDateFormat.parse(strDate);
      22. }
  • Threadlocal:可以为每个线程,创建一个对象的独立副本内存

    • set方法:本质上就是通过一个Map集合,以线程作为key,存储对象
    • get方法:在获取对象的时候直接以线程作为key获取存储的对象
    • initialValue:在线程使用ThreadLocal的时候如果返现存储的对象是null,即第一次使用的时候,会直接调用initialValue创建对象
      1. //使用ThreadLocal
      2. static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
      3. //在当前线程第一次调用get之前就初始化了存储的对象
      4. @Override
      5. protected SimpleDateFormat initialValue() {
      6. return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      7. }
      8. };