多线程一并发编程基础

57次阅读

共计 3568 个字符,预计需要花费 9 分钟才能阅读完成。

并发编程基础一

进程与线程

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

线程:是进程的一个执行单元,是进程内可调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

一个进程有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

怎么创建线程?

  1. 实现 Runnable Callable 接口

    – 实现 run() 方法 无返回值,无参数,难以传参;

    – 实现 call 方法 有返回值,可由 FutureTask<T> 类型变量接收结果,无参数,难以传参。结果通过 FutureTask 对象的 get() 方法获取。

  2. 继承 Thread 类

    – 实现 run() 方法 无返回值,无参数,由于继承特性,便于传参;

  3. 使用线程池技术复用线程

Java 中与线程相关的方法

Object 类作为共享资源的相关实例方法

  1. wait(); 当一个共享变量的 wait() 方法被调用时,该调用线程会被阻塞挂起,直到

    ​(1). 其他线程调用的该共享变量的 notify() 或 notifyAll() 方法唤醒;

    ​(2). 其他线程调用了此线程的 interrupt() 方法,使此线程抛出异常而返回;

    wait(long timeout); 执行此方法后,若该线程未在规定时间类被其他线程唤醒,则会抛出异常返回。

    wait(long timeout , int nanos); 当 0 < nanos <999999,timeout 增加 1;

  2. notify() 与 notifyAll(); 共享变量调用后,唤醒 一个 / 所有 被 wait 阻塞的线程,不放资源;

Thread 类相关方法

获取当前的 Thread 实例 Thread.currentThread();

  1. join(); 等待线程执行结束的方法。
  2. static sleep(); 当前线程等待时间后继续执行,不放 CPU;
  3. static yield(); 当前线程礼让,然后一起参与对 CPU 的竞争;
  4. void interrupt(); 将此线程标记为被中断状态的线程

    boolean isInterrupted(); 检测是否被标记为中断状态

    static boolean interrupted(); 检测,且若检测出中断状态,将会清除中断标记;

死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

死锁产生的条件:

互斥条件: 指进程对所分配到的资源进行排它性使用, 即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源, 则请求者只能等待, 直至占有资源的进程用毕释放。

请求和保持条件: 指进程已经保持至少一个资源, 但又提出了新的资源请求, 而该资源已被其它进程占有, 此时请求进程阻塞, 但又对自己已获得的其它资源保持不放。

不剥夺条件: 指进程已获得的资源, 在未使用完之前, 不能被剥夺, 只能在使用完时由自己释放。

环路等待条件 : 指在发生死锁时, 必然存在一个进程——资源的环形链, 即进程集合{P0,P1,P2,···,Pn} 中的 P0 正在等待一个 P1 占用的资源; P1 正在等待 P2 占用的资源,……, Pn 正在等待已被 P0。

一般能够打破的死锁条件为 请求并持有条件 环路等待条件

ThreadLocal 与 InheritableThreadLocal 原理

当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

//Thread 类的成员

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

让我们看一下 ThreadLocal 类存值的源码

//ThreadLocal 类
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
// 获取线程类存的 threadLocals inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

对于某一 ThreadLocal 来讲,他的索引值 i 是确定的,在不同线程之间访问时访问的是不同的 table 数组的同一位置即都为 table[i],只不过这个不同线程之间的 table 是独立的。

对于同一线程的不同 ThreadLocal 来讲,这些 ThreadLocal 实例共享一个 table 数组,然后每个 ThreadLocal 实例在 table 中的索引 i 是不同的。

ThreadLocal 不具继承性,InheritableThreadLocal 继承自 ThreadLocal,能够解决 ThreadLocal 不能访问父线程本地变量的问题。

ThreadLocal 的内存泄露问题

/*
    ThreadLocal.ThreadLocalMap 类的 Entry 的键节点 key 是弱引用,会在垃圾回收器回收垃圾时被垃圾回收,而值是被 Entry 对象强引用,不会被回收,但不能再通过 Entry 键的 key 找到引用的值 value。*/
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> k, Object v) {super(k);
        value = v;
    }
}

因为上述的原因,在 ThreadLocal 这个类的 get()、set()、remove()方法,均有实现回收 key 为 null 的 Entry 的 value 所占的内存。所以,为了防止内存泄露(没法访问到的内存),在不会再用 ThreadLocal 的线程任务末尾,调用一次 上述三个方法的其中一个即可。

例如:

private void remove(ThreadLocal<?> key) {Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    // 循环可能是因为解决 hash 冲突方法为开放地址
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();// 清除引用
            
            // 历遍过程 key 为 null 的引用,value 都会被赋值为 null,释放空间
            expungeStaleEntry(i);
            return;
        }
    }
}

方法

- void set(T value);设置当前线程的线程局部变量的值。- public T get();该方法返回当前线程所对应的线程局部变量。- public void remove();将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。- protected T initialValue();返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get()或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。

ThreadLocalRandom

Random 类的种子变量为支持 CAS 操作的变量,新的随机数生成需要两个步骤:

  1. 首先根据老种子生成新种子
  2. 然后根据新的种子生成新的随机数

在多线程环境下,由于多个线程同时竞争原子变量的更新操作,而 CAS 操作只能同时成功一个,这会导致大量线程不断进行自旋重试,这会降低并发性。

与 ThreadLocal 原理相同,将种子存于线程中,每个线程有自己的种子,从而做到线程安全。

ThreadLocalRandom 类通过 ThreadLocalRandom.current() 来获取当前线程的随机数生成器,此类为工具类,继承了 Random 类但没有使用其种子变量,而是调用 Thread 类中实例的 threadLocalRandomSeed 变量。

通过让每一个线程复制一份变量,使得每个线程对变量的操作是在本地副本上进行,从而避免竞争,提高性能。

其他

用户线程与守护线程的概念

参考书《Java 并发编程之美》

ThreadLocal 的内存泄露问题 参考:https://blog.csdn.net/yanluan…

正文完
 0