学习《java并发编程的艺术》Chapter4

线程间的通信机制:

    举个例子,多线程打印十次ABC后打印OVER,然后退出。用各种通信机制实现一遍。

    1)volatile和synchronized关键字等。都是借助指令来保证了所有线程对关键字修饰的共享变量的可见性。这边以ReantranLocak为例。

public class Test3 {

    private static volatile int abc = 0;
    private static final Lock LOCK = new ReentrantLock();

    private static final Condition A_CONDITION = LOCK.newCondition();
    private static final Condition B_CONDITION = LOCK.newCondition();
    private static final Condition C_CONDITION = LOCK.newCondition();
    private static final Condition OVER_CONDITION = LOCK.newCondition();

    public static void main(String[] args) throws Exception {
        new Thread(new PrintAThread()).start();
        new Thread(new PrintBThread()).start();
        new Thread(new PrintCThread()).start();
        new Thread(new PrintOverThread()).start();
        new Thread(() -> {
            LOCK.lock();
            try {
                A_CONDITION.signal();
            } finally {
                LOCK.unlock();
            }
        }).start();
    }

    static class PrintAThread implements Runnable {
        @Override
        public void run() {
            LOCK.lock();
            try {
                while (abc < 30) {
                    A_CONDITION.await();
                    System.out.print("A");
                    abc++;
                    B_CONDITION.signal();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        }
    }

    static class PrintBThread implements Runnable {
        @Override
        public void run() {
            LOCK.lock();
            try {
                while (abc < 30) {
                    B_CONDITION.await();
                    System.out.print("B");
                    abc++;
                    C_CONDITION.signal();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        }
    }

    static class PrintCThread implements Runnable {
        @Override
        public void run() {
            LOCK.lock();
            try {
                while (abc < 30) {
                    C_CONDITION.await();
                    System.out.println("C");
                    abc++;
                    if (abc != 30) {
                        A_CONDITION.signal();
                    }
                }
                OVER_CONDITION.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        }
    }

    static class PrintOverThread implements Runnable {
        @Override
        public void run() {
            LOCK.lock();
            try {
                OVER_CONDITION.await();
                System.out.println("OVER");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        }
    }

}

    2)等待通知机制。通过wait()/notify()/nodifyAll()方法实现。

public class Test1 {

    private static volatile int abc = 1;

    public static void main(String[] args) {
        PrintThread printA = new PrintThread(1, "A");
        PrintThread printB = new PrintThread(2, "B");
        PrintThread printC = new PrintThread(0, "C");
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        Future futureA = cachedThreadPool.submit(printA);
        Future futureB = cachedThreadPool.submit(printB);
        Future futureC = cachedThreadPool.submit(printC);
        try {
            futureA.get();
            futureB.get();
            futureC.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("OVER");
        cachedThreadPool.shutdown();
    }

    static class PrintThread implements Runnable{
        private int value;
        private String printChar;

        PrintThread(int value, String printChar) {
            this.value = value;
            this.printChar = printChar;
        }

        @Override
        public void run() {
            int count = 0;
            while (count < 10) {
                synchronized (Test1.class) {
                    if (abc % 3 == value) {
                        if ("C".equals(printChar)) {
                            System.out.println(printChar);
                        } else {
                            System.out.print(printChar);
                        }
                        abc++;
                        count++;
                        Test1.class.notifyAll();
                    } else {
                        try {
                            Test1.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

    3)管道输入/输出流。用管道来打印ABC跑的也忒慢了。。。呜呜呜,而且还有点小问题,有没有大佬指点一下。

public class Test {

    private static volatile AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        PipedWriter writerA = new PipedWriter();
        PipedReader readerA = new PipedReader();
        PipedWriter writerB = new PipedWriter();
        PipedReader readerB = new PipedReader();
        PipedWriter writerC = new PipedWriter();
        PipedReader readerC = new PipedReader();

        try {
            writerA.connect(readerA);
            writerB.connect(readerB);
            writerC.connect(readerC);
            Thread printThreadA = new Thread(new Print(readerC, writerA, "A"));
            Thread printThreadB = new Thread(new Print(readerA, writerB, "B"));
            Thread printThreadC = new Thread(new Print(readerB, writerC, "C"));
            printThreadA.start();
            printThreadB.start();
            printThreadC.start();
            writerC.write(0);
            printThreadA.join();
            printThreadB.join();
            printThreadC.join();
            System.out.println("OVER");
        } finally {
            writerA.close();
            writerB.close();
            writerC.close();
        }
    }

    static class Print implements Runnable {
        private PipedReader reader;
        private PipedWriter writer;
        private String printChar;

        Print(PipedReader reader, PipedWriter writer, String printChar) {
            this.reader = reader;
            this.writer = writer;
            this.printChar = printChar;
        }

        @Override
        public void run() {
            try {
                while (reader.read() != -1) {
                    if (count.intValue() >= 30) {
                        break;
                    }
                    count.getAndAdd(1);
                    if ("C".equals(printChar)) {
                        System.out.println(printChar);
                    } else {
                        System.out.print(printChar);
                    }
                    writer.write(0);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

    4)Thread.join()的使用

    5)ThreadLocal

        先说说ThreadLocal的数据结构。在ThreadLocal类中有一个静态内部类ThreadLocalMap,而ThreadLocalMap有一个成员属性是Entry数组,Entry是ThreadLocalMap类中的一个静态内部类。由Entry类的构造函数可以看出,一个Entry对象至少有两个成员属性:ThreadLocal<?> k 和Object value。也就是说,一个Thread中只有一个ThreadLocalMap对象,一个ThreadLocalMap对象可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中的一个Entry。

static class ThreadLocalMap {

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

    使用场景:每个线程需要有自己单独的实例;实例需要在多个方法中共享,但不希望被多线程共享。

    提一提Entry的父类WeakReference,也就是弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是value 是强引用,不会被清理,就会出现 key 为 null而value还在的Entry对象。为了避免内存泄漏等情况出现,在调用ThreadLocalMap类的set()、get()、remove()方法时,都会调用一个叫做expungeStaleEntry()的方法,用来清理掉key为null的Entry对象。但这仅限于ThreadLocal没有被外部强引用的情况下。如果有自定义的ThreadLocal对象存在,最好手动调用remove()方法回收该对象,尤其在线程池场景下,线程经常会被复用,可能会影响后续业务逻辑和造成内存泄露等问题。

线程池技术

    通过线程的复用,消除频繁创建和消亡线程的系统资源开销,面对过量任务的提交也能平缓恶化。

    Executors提供了四种常用的线程池,CacheThreadPool、FixedThreadPool、SingleThreadExecutor、ScheduledThreadPool,这里不单独做介绍,而是介绍他们的父类ThreadPoolExecutor。因为这四种常用线程池实际上都是通过ThreadPoolExecutor类来创建的。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池的七个参数详解:

    1)corePoolSize:核心线程数。当有任务进来的时候,如果当前线程池内线程的数量小于核心线程数,即使有空闲线程,也不会复用,而是新建一个线程来执行传入的任务。核心线程一般不会销毁,除非通过allowCoreThreadTimeOut(boolean value)方法设置allowCoreThreadTimeOut参数为true,给核心线程添加超时机制,超时了才会销毁。

    2)maximumPoolSize:最大线程数。当有任务进来且线程池内线程数不小于核心线程数且无空闲线程时,则会创建一个临时线程来执行该任务。临时线程有超时机制,超时则销毁。

    3)keepAliveTime:超时时间。与超时机制对应,当线程空闲时间达到超时时间时,则销毁该线程。

    4)unit:超时时间的单位,比如说秒,毫秒。

    5)workQueue:阻塞队列。当有任务进来时,任务会先进入到该队列中,调度的时候再从队列中取出来,交给线程执行。这个参数其实一定程度上决定了线程池的运行策略,因为从队列中取出任务的规则会决定线程池执行任务的顺序,而队列的有界和无界更有意思了。如果是无界队列的话,任务可以无限加入到该队列中;如果是有界队列的话,当任务数大于队列长度,那么新来的任务是无法加入队列的,如果没有核心线程来取走它,那么会在线程池中创建一个临时线程来执行该任务。

    6)threadFactory:用来创建线程的工厂。ThreadFactory是一个接口,通过该接口可以自定义实现生成线程的方式、定义线程名格式、是否后台执行等等,通常是用来定义线程名称的格式,用来排查问题。

    7)handler:拒绝策略。该策略只对有界队列有效。当线程池内线程数达到了最大线程数且都不空闲,而阻塞队列也放满了任务,那么就需要一个策略来应对新来的任务。

线程池添加任务的方式:

    由参数部分可以知道,若线程池内的线程想要执行任务,那么任务一定是从阻塞队列中取出来的。而线程池启动后,只有通过调用execute(Runnable commond)方法才能将任务放入队列中。在ThreadPoolExecutor类中,还有一个submit方法,该方法有三种传参方式,但是在方法内都调用了execute方法将任务添加到队列中,submit方法拓展的功能就是,能够得到一个Future<T>类型的返回值。Future是一个接口类,通过返回的Future对象,可以对对应的线程做取消/join操作和状态判定。

public interface Future<V> {

    /**
     * 试图取消当前任务的执行。如果任务已经执行完或已经被取消或因为某些原因无法取
     * 消,这次操作则会失败。如果任务尚未开始,那这个任务不会再执行了。如果任务正
     * 在运行,通过传参来决定是否需要中断线程来取消任务。
     *
     * @param mayInterruptIfRunning 传参的值为true的话,允许尝试中断线程来取消。
     * @return 是否取消成功。
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * @return 指定任务是否取消成功。
     */
    boolean isCancelled();

    /**
     * @return 指定任务是否已完成。
     */
    boolean isDone();

    /**
     * 阻塞当前线程,直到执行该任务的线程执行完毕,并返回任务执行的返回值。
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 阻塞当前线程,直到执行该任务的线程执行完毕,并返回任务执行的返回值。如果阻塞
     * 时间达到传参设定的超时时间,抛出超时异常。
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

通常情况下,线程池内一个工作线程完整的执行流程如下:

    execute(提交任务到阻塞队列中) => addWorker(创建一个Worker对象,该对象有一个成员变量是Thread,执行thread.start()方法) => runWorker(先取Worker对象的firstTask成员变量,如果为空,则调用getTask()方法去队列中取优先级最高的任务,取得任务且一系列状态判断通过后,执行该任务。工作线程不断循环该过程,直到Worker对象的firstTask成员变量为空且getTask()方法取到了空值) => processWorkerExit(通过该方法来管理线程池内的线程数,以及是否需要关闭线程池,如果Worker是异常结束的话,则立马addWorker) => tryTerminate(满足终结线程池条件且阻塞队列中任务数为0时,关闭线程池)

手动关闭线程池:

    手动关闭线程池有shutdown()和shutdownnow两个方法。shutdown()方法是将线程池状态置为shutdown状态,然后中断空闲线程,等正在运行的线程执行完毕后再关闭线程池;shutdownnow()方法是将线程池状态置为stop状态,并将所有存活的线程中断,再返回队列中尚未执行的任务列表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值