Java多线程编程一 并发编程基础(上)

并发编程基础一

进程与线程

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

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

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

怎么创建线程?

  1. 实现 Runnable Callable 接口

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

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

  1. 继承 Thread 类

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

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

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> {

    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/yanluandai1985/article/details/82590336

你可能感兴趣的:(Java多线程编程一 并发编程基础(上))