Java并发编程(五)

2019-06-15 00:00:00 java 编程 并发

一、Condition:    

condition基于接口实现,用于实现等待和唤醒线程,功能强大,使用灵活;     

通过使用condition可以指定条件来指定叫醒某个线程;      

Condition接口具有下面几个方法:    

(1)await():用于让线程进入等待状态;   

(2)signal(): 用于唤醒线程;    

(3)signalAll(): 用于唤醒所有线程;   

实例代码1:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName: ConditionDemo
 * @Description:  使用Condition保证线程的执行顺序
 * @Author: liuhefei
 * @Date: 2019/4/11
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ConditionDemo {

    private int signal;

    Lock lock = new ReentrantLock();
    Condition c1 = lock.newCondition();
    Condition c2 = lock.newCondition();
    Condition c3 = lock.newCondition();

    //线程1
    public void thread1(){
        lock.lock();   //加锁
        while (signal != 0){
            try {
                c1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread1....");
        signal++;
        c2.signal();  //唤醒线程2
        lock.unlock();   //释放锁
    }

    //线程2
    public void thread2(){
        lock.lock();  //加锁
        while (signal != 1){
            try {
                c2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread2....");
        signal++;
        c3.signal();   //唤醒线程3
        lock.unlock();  //释放锁
    }

    //线程3
    public void thread3(){
        lock.lock();   //加锁
        while (signal != 2){
            try {
                c3.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println("thread3....");
        signal = 0;
        c1.signal();  //唤醒线程1
        lock.unlock();  //释放锁
    }

    public static void main(String[] args) {
        ConditionDemo conditionDemo = new ConditionDemo();
        MyThread1 my1 = new MyThread1(conditionDemo);
        MyThread2 my2 = new MyThread2(conditionDemo);
        MyThread3 my3 = new MyThread3(conditionDemo);

        new Thread(my1).start();
        new Thread(my2).start();
        new Thread(my3).start();

    }

}

class MyThread1 implements Runnable{

    private ConditionDemo conditionDemo;

    public MyThread1(ConditionDemo conditionDemo){
        this.conditionDemo = conditionDemo;
    }

    @Override
    public void run() {
        while(true){
            conditionDemo.thread1();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class MyThread2 implements Runnable{

    private ConditionDemo conditionDemo;

    public MyThread2(ConditionDemo conditionDemo){
        this.conditionDemo = conditionDemo;
    }

    @Override
    public void run() {
        while(true){
            conditionDemo.thread2();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class MyThread3 implements Runnable{

    private ConditionDemo conditionDemo;

    public MyThread3(ConditionDemo conditionDemo){
        this.conditionDemo = conditionDemo;
    }

    @Override
    public void run() {
        while(true){
            conditionDemo.thread3();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

实例代码2:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName: ConditionDemo2
 * @Description: 使用condition按顺序唤醒线程
 * @Author: liuhefei
 * @Date: 2019/5/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ConditionDemo2 {

    private int signal;   //信号
    Lock lock = new ReentrantLock();
    Condition a = lock.newCondition();
    Condition b = lock.newCondition();
    Condition c = lock.newCondition();

    public void a(){
        lock.lock();
        while (signal != 0){
            try {
                a.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("a...");
        signal++;
        b.signal();   //唤醒b
        lock.unlock();
    }

    public synchronized void b(){
        lock.lock();
        while (signal != 1){
            try {
                b.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("b...");
        signal++;
        c.signal();   //唤醒c
        lock.unlock();
    }

    public synchronized void c(){
        lock.lock();
        while (signal != 2){
            try {
                c.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("c...");
        signal = 0;
        a.signal();  //唤醒a
        lock.unlock();

    }

    public static void main(String[] args) {
        ConditionDemo2 d = new ConditionDemo2();

        Thread1 a = new Thread1(d);
        Thread2 b = new Thread2(d);
        Thread3 c = new Thread3(d);

        new Thread(a).start();
        new Thread(b).start();
        new Thread(c).start();


    }
}

class Thread1 implements Runnable {

    private ConditionDemo2 demo;

    public Thread1(ConditionDemo2 demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        while(true) {
            demo.a();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class Thread2 implements Runnable {

    private ConditionDemo2 demo;

    public Thread2(ConditionDemo2 demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        while(true) {
            demo.b();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Thread3 implements Runnable {

    private ConditionDemo2 demo;

    public Thread3(ConditionDemo2 demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        while(true) {
            demo.c();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

二、ThreadLocal:        

(1)什么是ThreadLocal?     

ThreadLocal的实例代表了一个线程局部的变量,每个线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用空间来换取时间的方式,解决多线程中相同变量的访问线程安全问题。    

当多个线程针对同一个共享变量进行写操作的时候容易出现并发问题,会导致线程不安全;为了解决线程不安全问题,一般使用者在访问共享变量时需要进行适当的同步操作,而要完成同步操作一般是通过加锁来实现。通常情况下,我们可以使用JDK包提供的ThreadLocal来保证线程安全问题。       

ThreadLocal,它提供了线程本地变量,你可以这样理解,当你创建一个ThreadLocal变量时,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程对这个变量进行操作时,实际上操作的是自己本地内存里面的变量,从而避免了线程安全问题。       

当你创建一个ThreadLocal变量时,每个线程都会复制一个变量到自己的本地内存中,以便后续对这个变量进行操作。   

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

实例代码:

/**
 * @ClassName: ThreadLockDemo1
 * @Description: ThreadLocal实现计数器,多个线程之间互不影响
 * @Author: liuhefei
 * @Date: 2019/5/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadLocalDemo1 {

    private ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
        protected Integer initialValue(){
            return new Integer(0);
        };
    };

    public int getNext(){
        Integer value = count.get();
        value++;
        count.set(value);
        return value;
    }

    public static void main(String[] args) {
        ThreadLocalDemo1 th = new ThreadLocalDemo1();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + " " + th.getNext());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + " " + th.getNext());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + " " + th.getNext());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}

(2)ThreadLocal的实现原理:

《Java并发编程(五)》

1、Thread类中有一个threadLocals和一个inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当前线程第一次调用ThreadLocal的set或get方法时才会创建它们。

这两个变量的定义源码如下:

 ThreadLocal.ThreadLocalMap threadLocals = null;
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

因为每个线程可以关联多个ThreadLocal变量,所以将Thread里面的threadLocals变量设计为map结构。    

2、当使用ThreadLocal来创建线程的本地变量时,这个变量并不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面的。换句话说,就是ThreadLocal类型的本地变量存放在具体的线程内存空间中。

3、ThreadLocal通过set方法把value值放入调用线程的threadLocals变量里面保存起来,当调用线程调用它的get方法时,再从当前线程的threadLocals变量里面将其取出来使用。 如果这个调用线程一直存活着,那么这个本地变量会一直保存在调用线程的threadLocals变量中。   

如果你不在需要这个本地变量时,你可以通过调用ThreadLocal变量的remove方法,将其从当前线程的threadLocals变量中删除即可。

(3)ThreadLocal的重要方法:     

1、get方法: 获取ThreadLocal中当前线程共享变量的值。换句话说就是返回此线程局部变量的当前线程副本中的值。其源码如下:

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程threadLocals变量
    ThreadLocalMap map = getMap(t);
    //如果threadLocals不为null,则返回对应的本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //threadLocals为空,则初始化当前线程的threadLocals变量
    return setInitialValue();
}
private T setInitialValue() {
    //初始化为null
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //如果当前线程的threadLocals变量不为空
    if (map != null)
        map.set(this, value);
    else   //如果当前线程的threadLocals变量为空,就调用createMap()方法创建当前线程的变量
        createMap(t, value);
    return value;
}

2、set方法: 设置ThreadLocal中当前线程共享变量的值。换句话说就是将此线程局部变量的当前线程副本中的值设置为指定值。 其源码如下:

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //将当前线程作为ThreadLocalMap的key,去查找对应的线程变量,找到就设置
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else  //当第一次调用时,就创建当前线程对应的HashMap,也就是创建当前线程的threadLocals变量
        createMap(t, value);
}

其中getMap(t)方法是获取线程自己的变量threadLocals,如果getMap(t)方法返回值不为空,则把value值设置到threadLocals中。 如果返回空,则调用createMap(t, value)方法。     

threadLocals是一个HashMap结构,其中key就是当前ThreadLocal的实例对象引用,value是通过get()方法传递的值。      

3、remove方法: 移除ThreadLocal中当前线程共享变量的值。换句话说就是移除此线程局部变量当前线程的值。  如果此线程局部变量随后被当前线程调用读取,而在这期间当前线程没有设置其值,则将调用其 initialValue() 方法重新初始化其值,这将导致在当前线程多次调用 initialValue 方法。             

其源码如下:

public void remove() {
     //获取当前线程的threadLocals变量
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果不为空,就调用删除方法进行删除
     if (m != null)
         m.remove(this);
 }

4、initialValue方法: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。换句话说就是返回此线程局部变量的当前线程的“初始值”。       

这个方法是为了让子类覆盖设计的,默认缺省null。如果调用get()方法后又调用了remove()方法将会再一次调用此方法。          

其源码如下:

protected T initialValue() {
   return null;
}

实例代码:

public class ThreadLocalDemo2 {

    //创建一个ThreadLocal变量
    static ThreadLocal<String> local = new ThreadLocal<>();

    InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();

    //定义一个方法
    public static void stringPrint(String str){
        //打印当前线程本地内存中local 变量的值
        System.out.println("当前线程:" + str + ", 其local变量的值为: " + local.get());
        //清除当前线程本地内存中的local变量的值
        local.remove();
    }

    public static void main(String[] args) {
        //创建线程
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程threadOne中本地变量local的值
                local.set("one-1234");
                //调用函数
                stringPrint("threadOne");
                //打印本地变量的值
                System.out.println("本地变量local的值为:" + local.get());
            }
        });

        //创建线程
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程threadOne中本地变量local的值
                local.set("two-5678");
                //调用函数
                stringPrint("threadTwo");
                //打印本地变量的值
                System.out.println("本地变量local的值为:" + local.get());
            }
        });

         threadOne.start();
         threadTwo.start();

    }
}

总结:   

1. threadLocals变量会存在于每个线程的内部,该变量是HashMap类型,其中key为我们定义的ThreadLocal变量的对象引用,也就是其this引用,而value值则是我们调用其set()方法设置的指定值。      

2. 每个线程的本地变量都存放在自己的内存变量threadLocals中,如果当前线程一直存活,那么这些本地变量就会一直保存在其内存中,当内存不够时,就会出现内存溢出的情况,

因此我们在使用完threadLocals变量后,需要及时调用ThreadLocal的remove方法来删除对应线程的threadLocals中的本地变量。      

3. ThreadLocal不支持继承性。同一个ThreadLocal变量在父线程中被设置后,在子线程中是获取不到的。    

而要实现在子类和父类中都可以获取到ThreadLocal变量的本地变量值时,可以使用InheritableThreadLocal类来实现。    

4. InheritableThreadLocal类继承自ThreadLocal类,它实现了子线程可以访问在父线程中设置的本地变量。   

InheritableThreadLocal类的源码如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }
    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal类继承自ThreadLocal类,它重写了三个方法,它将当前线程变量的值存入inheritableThreadLocals变量中,该变量是ThreadLocal.ThreadLocalMap类型,定义如下:    

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

InheritableThreadLocal类通过重写getMap()方法和createMap()方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程在通过InheritableThreadLocal类实例的set()或get()方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中的inheritableThreadLocals变量里面的本地变量值复制一份到子线程的inheritableThreadLocals变量中,

最终实现子线程和父线程都可以访问到ThreadLocal变量中的本地变量值。

实例代码1:

/**
 * @ClassName: ThreadLocalDemo3
 * @Description: ThreadLocal不支持继承性,同一个ThreadLocal变量在父线程中被设置后,在子线程中是获取不到的。
 * @Author: liuhefei
 * @Date: 2019/5/19
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadLocalDemo3 {

    //创建ThreadLocal
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    //父线程main
    public static void main(String[] args) {
        //设置值
        threadLocal.set("Hello ThreadLocal");

        //创建子线程thread
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取当前线程
                System.out.println(Thread.currentThread().getName());
                //子线程输出线程变量的值
                System.out.println("子线程获取threadLocals值:" + threadLocal.get());
            }
        });

        //启动子线程
        thread.start();

        //主线程获取线程变量的值
        System.out.println(Thread.currentThread().getName() + "——线程变量的值为:" + threadLocal.get());
    }
}

实例代码2:

/**
 * @ClassName: ThreadLocalDemo5
 * @Description: InheritableThreadLocal实现子线程可以访问父线程的本地变量
 * @Author: liuhefei
 * @Date: 2019/5/20
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadLocalDemo5 {

    public static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        System.out.println("主线程启动了....");
        inheritableThreadLocal.set("Hello World");

        //创建子线程thread
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取当前线程
                System.out.println(Thread.currentThread().getName());
                //子线程输出线程变量的值
                System.out.println("子线程获取threadLocals值:" + inheritableThreadLocal.get());
            }
        });

        //启动子线程
        thread.start();

        //主线程获取线程变量的值
        System.out.println(Thread.currentThread().getName() + "——线程变量的值为:" + inheritableThreadLocal.get());

    }
}

更多关于并发编程的相关知识,后续会不断更新,感谢诸君的支持!

相关文章