zoukankan      html  css  js  c++  java
  • java 并发相关(1)

    注 :示例图片来源于网络

    1. 线程的基本概念

    • 进程

    一个程序启动,Linux系统会给程序分配一个进程,并且分配给进程一些内存资源,启动一个jar包,就会创建这个jar包的进程;线程可以看做进程的一个顺序执行的指令流,一个进程可以创建多个线程,同一个进程创建的线程共享进程的内存资源,不同进程创建的线程是相互隔离的。

    进程是系统资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

    • 线程

    线程可以看做一段顺序的指令序列,每个线程都有自己独立的堆栈和局部变量,多个线程之间共享父进程的地址空间和资源;创建线程的资源开销少,线程间的切换损耗少,所以java中采用多线程形式实现并发。

    线程是CPU调度的基本单位,依附于进程。

    • java 并发编程

    java实现的并发编程,即多个线程可以同时执行工作,提高cpu的使用效率,减少任务执行的总耗时;但多线程编程,也会带来缓存不一致,操作顺序难以控制的线程安全问题。

    根据运行环境CPU的不同,可以分为单核CPU并发(时间片轮转)、多核CPU并行两种。

    • 多线程的应用场景

    web服务器多个线程处理请求、后台任务、异步处理等场景。

    2. 如何创建一个线程

    java中主要通过下面三种方式创建一个线程:

    注:无论通过哪一种方式创建一个线程,java中都是使用Runnable接口的run()方法做线程的执行体,最后都需要new一个Thread对象来创建线程。

    • 继承Thread类

    java创建一个线程是通过新建一个Thread对象实现的,继承Thread类创建线程,会存在java单继承的局限性。

    public class MyThread extends Thread {
    
        public MyThread(){}
    
        public MyThread(ThreadGroup group, String name){
            super(group,name);
        }
    
        @Override
        public void run() {
            System.out.println("This is Mythread, name is "+ getName() + ",ThreadGroup is " + getThreadGroup());
        }
    
    }
    
    • 实现Runnable接口

    实现一个Runnable接口创建线程,重写run方法,缺点是没有返回值。

    public class MyRunnable implements Runnable{
    
        @Override
        public void run(){
            System.out.println("This is MyRunnable name is "+Thread.currentThread().getName());
        }
    }
    
    
    • 实现Callable接口

    实现一个call接口创建线程,需要和Future接口及相关的实现工具类FutureTask来实现一个有返回值的线程。

    public class MyCallable implements Callable<String> {
    
        @Override
        public String call() {
            System.out.println("This is MyCallable name is "+Thread.currentThread().getName());
    
            return "MyCallable";
        }
    }
    
            MyCallable callable = new MyCallable();
            FutureTask<String> futureTask = new FutureTask<String>(callable);
            new Thread(futureTask, "futureTask").start();
    
            try {
                System.out.println("拿到callable返回值:" + futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
    • 三者区别

    1、Runnable和Callable采用接口形式执行线程任务,比直接继承Thread执行线程任务更加灵活。
    2、Runnable是一个没有返回值的线程任务,Callable是有返回值的线程任务。
    3、Callabe接口一般和Future接口的实现类来组合实现一个有返回值的异步编程,Future接口的实现类有:FutureTask、CompletableFuture。

    3. 线程的一些关键属性

    • name:线程名称,可以重复,若没有指定会自动生成。

    • id:线程ID,一个全局唯一的正long值,创建线程时指定,终生不变,线程终结时ID可以复用。

    • priority:线程优先级,取值为1到10,线程优先级越高,执行的可能越大(跟CPU的线程调度模式有关),优先级还跟运行环境有关,若运行环境不支持优先级分10级,如只支持5级,那么设置5和设置6有可能是一样的。

      线程调度模式:java中采用抢占式调度

      1、分时调度模式:所有线程轮流使用CPU,平均分配每个线程占用的CPU时间片。

      2、抢占式调度模式:优先让优先级高的线程使用CPU,优先级高的线程获取CPU的时间片会多一些。

    • state:线程状态,Thread.State枚举类型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 6种,后续第4点详细介绍。

    • daemon:布尔值,是否为守护线程,java中线程分为两类:守护线程(true)和用户线程(false)。

      1、用户线程:一般我们创建的线程都是用户线程,当JVM中的用户线程全部执行完毕,JVM也会关闭。

      2、守护线程:只要JVM尚有一个非守护线程没有结束,守护线程就进行工作,守护线程随着JVM关闭而结束,典型例子:GC。

    • ThreadGroup:所属线程组,一个线程必然有所属线程组,线程组被设定为管理线程的一些属性,提供了一些管理线程组旗下线程的api方法和统一异常设置等。

    线程组以树形结构存在,每个线程组都有一组子线程和子线程组。

    • UncaughtExceptionHandler:未捕获异常时的处理器,默认没有,线程出现错误后会立即终止当前线程运行,并打印错误。

    4 线程的状态转换

    • 线程定义的状态有六种:
    Thread.State Thread中定义的几种状态
    NEW 尚未启动的线程状态,即线程创建,还未调用start方法
    RUNNABLE 就绪状态ready(调用start,等待调度)+正在运行running
    BLOCKED 等待监视器锁时,陷入阻塞状态
    WAITING 等待状态的线程正在等待另一线程执行特定的操作(如notify)
    TIMED_WAITING 具有指定等待时间的等待状态
    TERMINATED 线程完成执行,终止状态
    • 状态转化图:

    • 锁池和等待池

    其实线程竞争锁都是在线程处于运行态尝试获取CPU资源执行线程任务的时候发生锁的竞争。

    锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

    等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

    • wait()方法和sleep()方法

    wait()和sleep()区别

    1.wait()来自Object类,sleep()来自Thread类

    2.调用 sleep()方法,线程不会释放对象锁。而调用 wait() 方法线程会释放对象锁;

    3.sleep()睡眠后不出让系统资源,wait()让其他线程可以占用 CPU;

    4.sleep(millionseconds)需要指定一个睡眠时间,时间一到会自然唤醒。而wait()需要配合notify()或者notifyAll()使用

    5.notify()随机释放一个线程从等待池到锁池,notifyAll()释放所有的线程从等待池到锁池。

    6.wait()方法只能在持有该对象monitor对象的时候使用。

    5. Thread类的一些相关方法的源码

    5.1 Thread(...args[])构造函数(实际调用init()方法,构造方法不涉及到初始化逻辑,需要的话进行统一封装)

    init()方法(源码省略):可以看出子线程继承父线程的优先级、是否守护线程、父线程的线程组、可继承ThreadLocal等属性。

    关于学习到的一些记录与知识总结
  • 相关阅读:
    《七哥说道》第十八章:何处不风雨,套路说江湖
    《七哥说道》第十七章:北漂青年,人海茫茫
    《闲扯Redis四》List数据类型底层编码转换
    《闲扯Redis三》Redis五种数据类型之List型
    《闲扯Redis二》String数据类型之底层解析
    《闲扯Redis一》五种数据类型之String型
    Js解析Json数据获取元素JsonPath与深度
    《七哥说道》第十六章:程序员,江湖见
    Swagger2.9.2进入API界面报NumberFormatException异常
    绝顶高手必经之路【资源共享】
  • 原文地址:https://www.cnblogs.com/Zxq-zn/p/14747289.html
Copyright © 2011-2022 走看看