编写高质量代码:改善Java程序的151个建议(第9章:多线程和并发___建议125~131)

建议125:优先选择线程池java

建议126:适时选择不一样的线程池来实现安全

建议127:lock与synchronized是不同的多线程

建议128:预防线程死锁并发

建议129:适当设置阻塞队列的长度app

建议130:使用CountDownLatch协调子线程dom

建议131:CyclicBarrier 让多线程齐步走ide

建议125:优先选择线程池函数

建议126:适时选择不一样的线程池来实现工具

Java线程池原理及实现<最通俗易懂的讲解>性能

建议127:lock与synchronized是不同的

直接上代码:

package OSChina.Multithread;

import java.util.Calendar;

public class Task {
    public void doSomething() {
        try {
            // 每一个线程等待2秒钟,注意此时线程的状态转变为Warning状态
            Thread.sleep(2000);
        } catch (Exception e) {
            // 异常处理
        }
        StringBuffer sb = new StringBuffer();
        // 线程名称
        sb.append("线程名称:" + Thread.currentThread().getName());
        // 运行时间戳
        sb.append(",执行时间: " + Calendar.getInstance().get(Calendar.SECOND) + "s");
        System.out.println(sb);
    }
}
package OSChina.Multithread;

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

public class TaskWithLock extends Task implements Runnable{
    // 声明显示锁
    private final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            // 开始锁定
            lock.lock();
            doSomething();
        } finally {
            // 释放锁
           lock.unlock();
        }
    }
}
package OSChina.Multithread;

public class TaskWithSync extends Task implements Runnable{
    @Override
    public void run() {
        synchronized ("A"){
            doSomething();
        }
    }
}
package OSChina.Multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Client {
    public static void runTasks(Class<? extends Runnable> clz) throws Exception{
        ExecutorService es = Executors.newCachedThreadPool();
        System.out.println("***开始执行 " + clz.getSimpleName() + " 任务***");
        // 启动3个线程
        for (int i = 0; i < 3; i++) {
            es.submit(clz.newInstance());
        }
        // 等待足够长的时间,而后关闭执行器
        TimeUnit.SECONDS.sleep(10);
        System.out.println("---" + clz.getSimpleName() + "  任务执行完毕---\n");
        // 关闭执行器
        es.shutdown();
    }

    public static void main(String[] args) throws Exception{
        // 运行显示任务
        runTasks(TaskWithLock.class);
        // 运行内部锁任务
        runTasks(TaskWithSync.class);
    }
}

显示锁是同时运行的,很显然pool-1-thread-1线程执行到sleep时,其它两个线程也会运行到这里,一块儿等待,而后一块儿输出,这还具备线程互斥的概念吗?

而内部锁的输出则是咱们预期的结果,pool-2-thread-1线程在运行时其它线程处于等待状态,pool-2-threda-1执行完毕后,JVM从等待线程池中随机获的一个线程pool-2-thread-2执行,最后执行pool-2-thread-3,这正是咱们但愿的。

如今问题来了:Lock锁为何不出现互斥状况呢?

这是由于对于同步资源来讲显示锁是对象级别的锁,内部锁是类级别的锁,lock定义为多线程类的私有属性是起不到互斥做用的,除非把lock定义为全部线程的共享变量。

改一下代码,将lock定义在测试类中

// 声明显示锁
public static final Lock lock = new ReentrantLock();

除了这一点不一样以外,显示锁和内部锁还有什么区别呢?还有如下4点不一样:

一、Lock支持更细精度的锁控制:

假设读写锁分离,写操做时不容许有读写操做存在,而读操做时读写能够并发执行,这一点内部锁就很难实现。显示锁的示例代码以下: 

package OSChina.Multithread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Foo {
    // 可重入的读写锁
    private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    // 读锁
    private static final Lock r = rwl.readLock();
    // 写锁
    private static final Lock w = rwl.writeLock();

    // 多操做,可并发执行
    public static void read() {
        try {
            r.lock();
            Thread.sleep(1000);
            System.out.println("read......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            r.unlock();
        }
    }

    // 写操做,同时只容许一个写操做
    public static void write() {
        try {
            w.lock();
            Thread.sleep(1000);
            System.out.println("write.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            w.unlock();
        }
    }
}

能够编写一个Runnable实现类,把Foo类做为资源进行调用(注意多线程是共享这个资源的),而后就会发现这样的现象:读写锁容许同时有多个读操做但只容许一个写操做,也就是当有一个写线程在执行时,全部的读线程都会阻塞,直到写线程释放锁资源为止,而读锁则能够有多个线程同时执行。

二、Lock锁是无阻塞的,synchronized是阻塞的

三、Lock可实现公平锁,synchronized只能是非公平锁

四、Lock是代码级的,synchronized是JVM级的

Lock是经过编码实现的,synchronized是在运行期由JVM释放的,相对来讲synchronized的优化可能性高,毕竟是在最核心的部分支持的,Lock的优化须要用户自行考虑。

显示锁和内部锁的功能各不相同,在性能上也稍有差异,但随着JDK的不断推动,相对来讲,显示锁使用起来更加便利和强大,在实际开发中选择哪一种类型的锁就须要根据实际状况考虑了:灵活、强大选择lock,快捷、安全选择synchronized。

建议128:预防线程死锁

一、死锁的概念

死锁是指多个进程在运行过程当中因争夺资源而形成的一种僵局。当进程处于僵持状态时,若无外力做用,它们都将没法再向前推动。

二、产生死锁的缘由

① 竞争资源

可剥夺资源和非剥夺性资源:

进程在得到这类资源后,该资源能够再被其它线程剥夺,CPU和主存均属于可剥夺性资源。另外一类资源是不可剥夺性资源,当系统把这类资源分配给某进程后,再不能强行回收,只能在进程用完后自行释放,如磁带机、打印机等。 

竞争非剥夺性资源:

在系统中所配置的非剥夺性资源,因为它们的数量不能知足诸进程运行的须要,会使进程在运行过程当中,因争夺这些资源而陷入僵局。

竞争临时资源:

临时资源是指由一个进程产生,被另外一个进程使用短暂时间后变无用的资源,它也可能产生死锁。

② 进程间推动顺序非法

进程在运行过程当中,请求和释放资源的顺序不当,一样会产生死锁。

三、死锁的一些经常使用概念:

① 互斥条件:

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

② 请求和保持条件:

指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其它进程占有,此时请求进程阻塞,但又对本身得到的其它资源保持不放。

③ 不剥夺资源:

指进程已得到资源,在使用完以前,不能被剥夺,只能在使用完时由本身释放。

④ 环路等待条件:

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

建议129:适当设置阻塞队列的长度

ArrayBlockingQueue类最经常使用的add方法

若是直接调用offer方法插入元素, 在超出容量的状况下, 它除了返回false外, 不会提供任何其余信息, 若是代码不作插入判断, 那就会形成数据的“默默”丢失, 这就是它与非阻塞队列的不一样之处。

若是应用指望不管等待多长时间都要运行该任务, 不但愿返回异常就须要用BlockingQueue接口定义的put方法了, 它的做用也是把元素加入到队列中, 但它与add、offer方法不一样, 它会等待队列空出元素, 再让本身加入进去, 通俗地讲, put方法提供的是一种“无赖”式的插入, 不管等待多长时间都要把该元素插入到队列中。
与插入元素相对应, 取出元素也有不一样的实现, 例如remove、poll、take等方法, 对于此类方法的理解要创建在阻塞队列的长度固定的基础上, 而后根据是否阻塞、阻塞是否超时等实际状况选用不一样的插入和提取方法。

建议130:使用CountDownLatch协调子线程

CountDownLatch是一个很是实用的多线程控制工具类。经常使用的就下面几个方法:

CountDownLatch(int count) //实例化一个倒计数器,count指定计数个数
countDown() // 计数减一
await() //等待,当计数减到0时,全部线程并行执行

对于倒计数器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,每每还要进行各项设备、仪器的检测。只有等到全部的检查完毕后,引擎才能点火。那么在检测环节固然是多个检测项能够同时进行的。代码实现:

public class CountDownLatchDemo implements Runnable{

    static final CountDownLatch latch = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {
        // 模拟检查任务
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //计数减一
            //放在finally避免任务执行过程出现异常,致使countDown()不能被执行
            latch.countDown();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i=0; i<10; i++){
            exec.submit(demo);
        }

        // 等待检查
        latch.await();

        // 发射火箭
        System.out.println("Fire!");
        // 关闭线程池
        exec.shutdown();
    }
}

上述代码中咱们先生成了一个CountDownLatch实例。计数数量为10,这表示须要有10个线程来完成任务,等待在CountDownLatch上的线程才能继续执行。latch.countDown();方法做用是通知CountDownLatch有一个线程已经准备完毕,倒计数器能够减一了。latch.await()方法要求主线程等待全部10个检查任务所有准备好才一块儿并行执行。

建议131:CyclicBarrier 让多线程齐步走

CyclicBarrier中文意思是“循环栅栏”

一、构造函数:

public CyclicBarrier(int parties)//parties 是参与线程的个数
public CyclicBarrier(int parties, Runnable barrierAction)//barrierAction是最后一个到达线程要作的任务

二、重要方法:

public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
  • 线程调用 await() 表示本身已经到达栅栏
  • BrokenBarrierException 表示栅栏已经被破坏,破坏的缘由多是其中一个线程 await() 时被中断或者超时

一个线程组的线程须要等待全部线程完成任务后再继续执行下一次任务,代码实例:

package OSChina.Multithread;

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    static class TaskThread extends Thread {

        CyclicBarrier barrier;

        public TaskThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(getName() + " 到达栅栏 A");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 A");

                Thread.sleep(2000);
                System.out.println(getName() + " 到达栅栏 B");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 B");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int threadNum = 5;
        CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 完成最后任务");
            }
        });

        for(int i = 0; i < threadNum; i++) {
            new TaskThread(barrier).start();
        }
    }
}

从打印结果能够看出,全部线程会等待所有线程到达栅栏以后才会继续执行,而且最后到达的线程会完成 Runnable 的任务。

三、CyclicBarrier 使用场景

能够用于多线程计算数据,最后合并计算结果的场景。

四、CyclicBarrier 与 CountDownLatch 区别

① CyclicBarrier 是可循环利用的,CountDownLatch 是一次性的

② CyclicBarrier 参与的线程职责是同样的,CountDownLatch 参与的线程的职责是不同的,有的在倒计时,有的在等待倒计时结束。

 

编写高质量代码:改善Java程序的151个建议@目录

相关文章
相关标签/搜索