java并发(一)进程和线程

一,进程和线程

1.1 进程和线程

1.1.1 进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,都必须将指令加载到CPU,数据加载到内存,在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令,管理内存,和管理IO的
  • 当一个程序运行,程序代码从磁盘加载到内存就相当于开启了一个进程
  • 进程相当于一个程序的实例

1.1.2 线程

  • 一个进程中有一到多个线程
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定顺序交给CPU执行
  • java中,线程是最小调度单位,进程是资源分配的最小单位

1.1.3 区别

  • 进程拥有共享的资源,如内存空间,供其内部的线程共享
  • 进程间的通信较为复杂
    • 同一台计算机的进程通信称为IPC
    • 不同计算机之前的进程通过网络通信
  • 线程通信较为简单,因为他们共享进程内的内存
  • 线程更加轻量,上下文切换成本更低

1.2 并行和并发

单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感 觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行

一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent

在这里插入图片描述

多核 cpu下,每个 核(core)都可以调度运行线程,这时候线程可以是并行的。

在这里插入图片描述

1.3 java中的线程

现代操作系统在运行一个程序时,会为其创建一个进程,例如,启动一个java程序,操作系统会创建一个java进程

现代操作系统调度的最小单位是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能访问共享的内存变量,处理器在这些线程上高速切换

1.3.1 创建线程

1.继承Thread,重写run方法

    public static class ThreadClass extends Thread{
        @Override
        public void run() {
            System.out.println("继承Thread,重写run方法");
        }
    }

启动线程

        Thread t5 = new ThreadClass();
        t5.setName("t5");
        t5.start();

2.实现runnable接口,重写run方法

    public static class RunClass implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"  分离runnable");
        }
    }

启动线程

        Thread t2 = new Thread(new RunClass());
        t2.setName("t2");
        t2.start();

3.实现callable接口,重写call方法,可以获取到返回值

    private static int i = 1;
    public static class CallClass implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            for(int a = 0;a < 100000;a++)
                i++;
            return i++;
        }
    }

启动线程

        FutureTask<Integer> futureTask = new FutureTask<>(new CallClass());
        Thread t4 = new Thread(futureTask);
        t4.setName("t4");
        t4.start();
        //获取返回值
        int i = futureTask.get();

我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟 机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

线程上下文切换

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

  • 线程的CPU时间片用完
  • 线程被垃圾回收
  • 有更高优先级的线程要运行
  • 线程自己调用了sleep、yield、wait、join、park、synchronized、lock 等方法

观察栈桢

    public static void main(String[] args) {
        new Thread(()->{
            method1(10);
        },"t1").start();
        new Thread(()->{
            method1(100);
        },"t2").start();
    }

    public static void method1(int a){
        method2(a++);
    }

    public static void method2(int b){
        method3(b++);
    }

    public static void method3(int c){
        int a = c++;
    }

断点注意选择Thread

在这里插入图片描述

在这里插入图片描述

当一个方法执行完退出了,该方法对应的栈桢就会出栈,相应的内存也会被回收

对于线程t2,执行的方法和线程t1互不影响

在这里插入图片描述

当前所有线程

在这里插入图片描述

1.3.2 启动线程start()和run()

  • 直接调用run()并没有开启新的线程,只是在当前线程上调用run方法而已
  • 使用start方法是启动新的线程,通过新的线程执行run方法
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程:"+Thread.currentThread().getName());
            }
        },"t1");
        t1.run();
    }

在这里插入图片描述

一个新构造的线程对象是由其parent线程来进行空间分配

一个线程必须由另外的一个线程来创建完成。对于其他所有的线程最终都是由main线程来创建的。而此时调用currentThread方法的时候获取到的就是main线程。

1.3.3 sleep()和yeild方法

sleep():

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读

yeild():

  • 让出CPU,从Running转为runnable让其他线程执行

1.3.4 join()方法

join():

  • 如果线程A执行了thread.join(),作用就是,线程A等待线程thread终止之后才从thread.join()返回

join(long time)

  • 还提供了超时等待,如果超过指定时间,就会直接返回,不等线程结束
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1执行1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1执行2");
        },"t1");
        t1.start();

        System.out.println("主线程执行");
        t1.join();
        System.out.println("等待结束,主线程继续执行");
    }

在这里插入图片描述

join()一般用于一个线程需要等待另一个线程的返回结果才能继续运行下去的情况,所以一定要得到另一个线程终止为止,也就是适用于同步的情况

同步,异步:

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

1.3.5 interrupt()方法

interrupt():

  • 打断 sleep,wait,join的线程,这几个方法都会让线程进入阻塞状态

中断sleep():

被中断的线程会抛出中断异常,从而从sleep中苏醒,这也是为什么sleep会捕获中断异常的原因,而且打断 sleep 的线程,会清空打断状态,即isInterrupted()返回false

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1执行1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1执行2");
        },"t1");
        t1.start();

        System.out.println("主线程执行");
        Thread.sleep(1000);
        t1.interrupt();
        System.out.println("主线程中断t1");
    }

在这里插入图片描述

打断正常运行的线程不会清除打断状态

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1执行1");
            while(true){

            }
        },"t1");
        t1.start();

        System.out.println("主线程执行");
        Thread.sleep(1000);
        t1.interrupt();
        System.out.println("主线程中断t1");
        System.out.println("t1中断状态字段:"+t1.isInterrupted());
    }

在这里插入图片描述

中断join():

中断join也会清除打断状态

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1执行1");
            while(true){

            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(()->{
            System.out.println("t2等待t1执行完");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t2被中断了");
            }
        },"t2");
        t2.start();

        System.out.println("主线程中断t2");
        Thread.sleep(1000);
        t2.interrupt();
        System.out.println("t2中断状态字段:"+t2.isInterrupted());
    }

在这里插入图片描述

中断判断

isInterrupted()不会清除打印标记, Thread.interrupted()静态方法,会清除打印标记

    public static void test() throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("before");
            LockSupport.park();
            System.out.println("after");
            //System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
            System.out.println("打断状态:" + Thread.interrupted());
            System.out.println("interrupted after");
        });
        t1.start();

        Thread.sleep(3000);

        t1.interrupt();
        t1.join();
        System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
    }

1.3.6 守护线程

其他非守护线程终止了,守护线程即使没终止,也会终止

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1是守护线程");
            while(true){

            }
        },"t1");
        t1.setDaemon(true);
        t1.start();

        Thread t2 = new Thread(()->{
            System.out.println("t2执行");
        },"t2");
        t2.start();
        System.out.println("主线程执行");
    }

1.3.7 线程状态

五种状态模型

v

六种状态模型

在这里插入图片描述

1.3.8 wait/notify

monitor中的owner线程发现条件不满足时调用wait方法,进入waitSet等待,线程状态变为WAITING,BLOCKED和WAITING状态的线程都处于阻塞状态,不占用CPU的时间片,BLOCKED线程会在owner线程释放时唤醒,WAITING线程则在owner线程调用notify和notifyAll时唤醒,唤醒后重新进入entrylist竞争获取锁

使用模板:

//        //使用模板
//        synchronized (lock) {
//            while (条件) {
//                //条件不成立
//                lock.wait();
//            }
//            //条件成立,继续逻辑
//        }
//        synchronized (lock){
//            //唤醒所有线程
//            lock.notifyAll();
//        }

wait和sleep的区别:

  • sleep是Thread的静态方法,sleep是object的实例方法
  • sleep无需与synchronized配合使用,wait需要获取锁配合synchronized配合使用
  • sleep睡眠时不释放锁,wait会释放锁

猜你喜欢

转载自blog.csdn.net/weixin_41922289/article/details/104666185