美文网首页
Java并发(四):并发编程基础

Java并发(四):并发编程基础

作者: Jorvi | 来源:发表于2019-02-20 10:42 被阅读0次

    一. 线程简介

    1. 什么是线程

    现代操作系统在运行一个程序时,会为其创建一个进程;
    一个进程里可以创建多个线程,线程是现代操作系统调度的最小单元。

    线程拥有各自的计数器、栈和局部变量等属性,能够访问共享的内存变量。

    2. 线程的状态

    Java线程的生命周期有6种可能的状态:

    状态名称 说明
    NEW 初始状态,线程被构建,但是还没有调用start()方法
    RUNNABLE 运行状态,操作系统中就绪和运行两种状态的统称
    BLOCKED 阻塞状态,表示线程阻塞于锁
    WAITING 等待状态,进入该状态表示当前线程需要等待其他线程的特定动作(通知或中断)
    TIME_WAITING 超时等待状态,可以在指定的时间自行返回
    TERMINATED 终止状态,表示当前线程已经执行完毕

    3. Daemon线程

    Daemon线程是一种支持型线程,主要用于程序中后台调度以及支持性工作。

    当Java虚拟机中除了Daemon线程没有其他线程的时候,Java虚拟机将退出。

    当Java虚拟机退出时,Daemon线程中的finally块并不一定会执行,因此不能依靠finally块来确保关闭清理资源。

    在线程启动前,可以利用thread.setDaemon(true)将线程设置为Daemon线程。

    二. 启动和终止线程

    1. 构造线程

        /**
         * Initializes a Thread.
         *
         * @param g the Thread group
         * @param target the object whose run() method gets called
         * @param name the name of the new Thread
         * @param stackSize the desired stack size for the new thread, or
         *        zero to indicate that this parameter is to be ignored.
         * @param acc the AccessControlContext to inherit, or
         *            AccessController.getContext() if null
         */
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name;
            
            // 当前线程就是该线程的父线程
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */
    
                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                /* If the security doesn't have a strong opinion of the matter
                   use the parent thread group. */
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
    
            /* checkAccess regardless of whether or not threadgroup is
               explicitly passed in. */
            g.checkAccess();
    
            /*
             * Do we have the required permissions?
             */
            if (security != null) {
                if (isCCLOverridden(getClass())) {
                    security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
                }
            }
    
            g.addUnstarted();
    
            this.group = g;
    
            // 将daemon、priority属性设置为父线程对应属性
            this.daemon = parent.isDaemon();
            this.priority = parent.getPriority();
            if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
            else
                this.contextClassLoader = parent.contextClassLoader;
            this.inheritedAccessControlContext =
                    acc != null ? acc : AccessController.getContext();
            this.target = target;
            setPriority(priority);
            if (parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            tid = nextThreadID();
        }
    

    新构造的线程对象的父线程就是当前线程。

    2. 启动线程

    线程对象初始化完成后,调用start()方法就可以启动这个线程。

    线程start()方法的含义是:
    当前线程(parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。

    3. 中断线程

    中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。

    线程通过方法isInterrupted()判断是否被中断。

    4. 安全的终止线程

    1. 利用interrupt()去中断线程A;
    2. 在线程A内利用isInterrupted()判断是否被中断,如果被中断了则清理资源,终止线程。

    如果武断的终止一个线程(例如过期的stop()方法),可能会导致线程资源无法正常释放。

    三. 线程间通信

    1. volatile和synchronized关键字

    • volatile关键字用于修饰字段(成员变量)时,表示任何对该变量的访问均需从主内存中获取,而对它的修改必须同步刷新回主内存,保证了所有线程对该变量的内存可见性。

    • synchronized关键字修饰方法或以同步块的形式使用时,确保了多个线程在同一时刻,只能有一个线程处于方法或同步块中,且同步方法或同步块开始时必须从主内存中读取相应的共享变量,同步方法或同步块结束时必须同步对应的共享变量回主内存,保证了多个线程对该变量的内存可见性和排他性。

    任意一个对象都拥有自己的监视器(Monitor)。

    1. 当这个对象的同步方法或包含该对象的同步块被调用时,执行线程需要先获取该对象的监视器;
    2. 如果成功获取了该对象的监视器,即给对象加锁成功,可以继续执行;
    3. 如果获取该对象的监视器失败,则执行线程进入同步队列,线程状态变为BLOCKED;
    4. 当加锁成功的线程释放了锁,则唤醒同步队列中被阻塞的线程,使其重新尝试获取该对象的监视器。

    2. 等待/通知机制

    任意一个对象都具备等待/通知的相关方法。

    方法名称 描述
    notify() 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该等待线程获取到了对象的锁
    notifyAll() 通知所有等待在该对象上的线程
    wait() 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用wait()方法后,会释放对象的锁
    wait(long) 超时等待一段时间(毫秒),如果没有通知就超时返回
    wait(long,int) 对于超时时间更细粒度的控制,可以达到纳秒
    1. Thread A成功给对象加锁,执行相应的代码;
    2. 当Thread A执行到Object.wait(),则释放对象锁,进入等待队列,Thread A状态变为WAITING;
    3. Thread B成功给对象加锁,执行相应的代码;
    4. 当Thread B执行到Object.notify()/notifyAll(),发送通知给等待队列,等待队列中Thread A出等待队列,进入同步队列,Thread A状态变为BLOCKED;
    5. Thread B继续执行后续代码,执行完成后释放对象锁;
    6. 收到锁释放的通知,同步队列中Thread A尝试获取对象锁,如果获取成功,则从wait()方法处返回,继续执行后续代码,执行完成后释放对象锁。

    3. Thread.join()

    如果线程A执行thread.join()语句,表示:
    当前线程A将一直等待,直到thread线程终止之后,才会从thread.join()处返回。

    对应的超时方法为:join(long millis)

    4. ThreadLocal

    ThreadLocal,即为线程变量,可以理解为类似于String、Integer这样变量类型。

    利用set(T)方法设置一个值;在当前线程下利用get()方法获取到之前设置的值。

        public static void main(String[] args) {
            ThreadLocal<Long> threadLocal = new ThreadLocal<>();
            
            threadLocal.set(System.currentTimeMillis());
    
            System.out.println(threadLocal.get());
        }
    

    相关文章

      网友评论

          本文标题:Java并发(四):并发编程基础

          本文链接:https://www.haomeiwen.com/subject/ouuarqtx.html