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

一. 线程简介

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 threadLocal = new ThreadLocal<>();
        
        threadLocal.set(System.currentTimeMillis());

        System.out.println(threadLocal.get());
    }

你可能感兴趣的:(Java并发(四):并发编程基础)