美文共赏

并发--基本概念

2021-12-10  本文已影响0人  zhemehao819

一、基本概念

1、进程与线程

进程

线程

二者对比

2、并发与并行

并发 (concurrent)

1583408729416

并行

二者对比

3、同步和异步

以调用方的角度讲

1 设计

多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这5秒cpu什么都做不了,其它代码都得暂停。

2 结论

二、Java线程

1、线程的创建

方法1、继承于Thread类,重写run()方法。

public class CreateThread {
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        // 启动线程
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("my thread running...");
    }
}

缺点:Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与线程代码耦合,当多个线程执行一样的任务时需要多份任务代码。

下面是使用匿名内部类的方式创建:

public static void main(String[] args) {
        // 匿名内部类方式创建 Thread
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running");
            }
        };
        
        t.start();
        log.debug("running");
    }

方法2、使用Runnable配合Thread(推荐)

实现Runnable接口,并且实现run()方法。在创建线程时作为参数传入该类的实例即可。

public class CreateThread2 {

   private static class MyRunnable implements Runnable {
      @Override
      public void run() {
         System.out.println("my runnable running...");
      }
   }

   public static void main(String[] args) {
      Thread thread = new Thread(new MyRunnable());
      thread.start();
   }
}

使用lambda表达式简化
当一个接口带有@FunctionalInterface注解时,是可以使用lambda来简化操作的
所以方法二中的代码可以被简化为:

public class Test2 {
    public static void main(String[] args) {
        //创建线程任务
        Runnable r = () -> {
            //直接写方法体即可
            System.out.println("Runnable running");
            System.out.println("Hello Thread");
        };
        //将Runnable对象传给Thread
        Thread t = new Thread(r);
        t.start();
    }
}

通过查看源码可以发现,方法2其实还是通过使用 Thread 类中的 run 方法执行的,Thread类中的run方法是先判断target(Runnable实现类)不为空时就调用target中的run方法。

方法3、通过Callable和FutureTask创建线程

public class Test3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //需要传入一个Callable对象
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("线程执行!");
                Thread.sleep(1000);
                return 100;
            }
        });

        Thread r1 = new Thread(task, "t2");
        r1.start();
        //获取线程中方法执行后的返回结果
        System.out.println(task.get());
    }
}

方法4、使用线程池

class NumberThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class Number2Thread implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for(int i = 1;i<=10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            sum+=i;
        }
        return sum;
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//创建一个可重用固定线程数为10的线程池

        //查看该对象是哪个类造的
        System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
        //设置线程池的属性
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合使用于Runnable
        Future future = service.submit(new Number2Thread());//适合使用于Callable
        try {
            System.out.println(future.get());//输出返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //3.关闭连接池
        service.shutdown();
    }
}

runnable 和 callable 有什么区别?

相同点:

主要区别:

2、线程的运行原理

栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 内存由堆、栈、方法区所组成,其中栈内存是给谁用的呢?

线程上下文切换(Thread Context Switch

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

3、常用方法

(1)start() vs run()

(2)sleep()与yield()

sleep (使线程阻塞)

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),可通过state()方法查看;
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 。如:
    //休眠一秒
    TimeUnit.SECONDS.sleep(1);
    //休眠一分钟
    TimeUnit.MINUTES.sleep(1);
    

在while(true)的情况下可以使用sleep来避免cpu空转(持续100%),sleep可以使当前线程状态变成time_waiting状态。(无需加锁就能实现)

yield (让出当前线程)

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

(3)join()方法

用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。
如在主线程中调用ti.join(),则是主线程等待t1线程结束

Thread thread = new Thread();
//等待thread线程执行结束
thread.join();
//最多等待1000ms,如果1000ms内线程执行完毕,则会直接执行下面的语句,不会等够1000ms
thread.join(1000);

(4)interrupt()方法

用于打断阻塞(sleep wait join…)的线程。 处于阻塞状态的线程,CPU不会给其分配时间片。

每一个线程都有一个boolean类型的标志,此标志是当前的请求是否请求中断,默认为false。当一个线程A调用了线程B的interrupt方法时,那么线程B的是否请求的中断标志变为true。而线程B可以调用方法检测到此标志的变化。

while(true) {
    if(Thread.currentThread().isInterrupted()) {
        break;
    }
}
//用于查看打断标记,返回值被boolean类型
t1.isInterrupted();
//静态方法检测中断标志,并检测完后重置false
Thread.interrupted();

interrupt方法的应用——两阶段终止模式

当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。

代码:

public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Monitor monitor = new Monitor();
        monitor.start();
        Thread.sleep(3500);
        monitor.stop();
    }
}

class Monitor {

    Thread monitor;

    /**
     * 启动监控器线程
     */
    public void start() {
        //设置线控器线程,用于监控线程状态
        monitor = new Thread() {
            @Override
            public void run() {
                //开始不停的监控
                while (true) {
                    //判断当前线程是否被打断了
                    if(Thread.currentThread().isInterrupted()) {
                        System.out.println("处理后续任务");
                        //终止线程执行
                        break;
                    }
                    System.out.println("监控器运行中...");
                    try {
                        //线程休眠
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //如果是在休眠的(打断的是阻塞方法)时候被打断,打断标记置为true,但抛出InterruptedException异常后又重置为false。所以此时需要再次设置为true
                        Thread.currentThread().interrupt();//非阻塞方法设置打断标记为true
                    }
                }
            }
        };
        monitor.start();
    }

    /**
     *  用于停止监控器线程
     */
    public void stop() {
        //打断线程
        monitor.interrupt();
    }
}

(5)不推荐使用的打断方法

(6)守护线程

当JAVA进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,JAVA进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。

//将线程设置为守护线程, 默认为false
monitor.setDaemon(true);

守护线程的应用

4、线程的状态

(1)五种状态

操作系统 层面来描述:

(2)六种状态

Java API 层面来描述:
根据 Thread.State 枚举,分为六种状态

演示线程6中状态:

@Slf4j(topic = "heyuanjun.test")
public class Thread6StatusCode {

    public static void main(String[] args) {

        //NEW
        Thread t1 = new Thread(() -> {
            log.info("----t1-----");
        }, "t1");

        //RUNNABLE
        Thread t2 = new Thread(() -> {
            while (true) {
                // log.info("----t2-----");
            }
        }, "t2");
        t2.start();

        //TERMINATED
        Thread t3 = new Thread(() -> {
            log.info("----t3-----");
        }, "t3");
        t3.start();

        //time_waiting
        Thread t4 = new Thread(() -> {
            synchronized (Thread6StatusCode.class) {
                try {
                    Thread.sleep(10 * 1000);
                    log.info("----t4-----");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t4.start();

        //waiting
        Thread t5 = new Thread(() -> {
            try {
                t2.join();
                log.info("----t5-----");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t5.start();

        //blocked
        Thread t6 = new Thread(() -> {
            synchronized (Thread6StatusCode.class) {
                log.info("----t6-----");
            }
        });
        t6.start();


        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("t1 线程状态:{}", t1.getState());
        log.info("t2 线程状态:{}", t2.getState());
        log.info("t3 线程状态:{}", t3.getState());
        log.info("t4 线程状态:{}", t4.getState());
        log.info("t5 线程状态:{}", t5.getState());
        log.info("t6 线程状态:{}", t6.getState());
        log.info("============结束==============");
    }
}

执行结果:

13:39:38.408 [t3] INFO heyuanjun.test - ----t3-----
13:39:40.406 [main] INFO heyuanjun.test - t1 线程状态:NEW
13:39:40.408 [main] INFO heyuanjun.test - t2 线程状态:RUNNABLE
13:39:40.408 [main] INFO heyuanjun.test - t3 线程状态:TERMINATED
13:39:40.408 [main] INFO heyuanjun.test - t4 线程状态:TIMED_WAITING
13:39:40.408 [main] INFO heyuanjun.test - t5 线程状态:WAITING
13:39:40.408 [main] INFO heyuanjun.test - t6 线程状态:BLOCKED
13:39:40.408 [main] INFO heyuanjun.test - ============结束==============
13:39:48.405 [t4] INFO heyuanjun.test - ----t4-----
13:39:48.405 [t6] INFO heyuanjun.test - ----t6-----
上一篇 下一篇

猜你喜欢

热点阅读