多线程笔记(三)

7.1Executor框架

为了更好的控制多线程,JDK提供了一台线程框架Execator,帮助开发人员有效的进行线程控制。是JDK并发包的核心。我们平时用的最多的便是Executors工厂类,这个工厂类提供了能产生多个不同功能线程池的方法。

newFixedThreadPool()方法:具有固定数量的线程池,线程数量始终不变。当有一个新任务提交时,线程中若有空闲进程变会执行它。若没有,则新的任务会被暂停在一个任务队列中。
newCachedThreadPool()方法:线程数量不固定,当线程不足时便会产生新的线程。
newSingleThreadExecutor()方法:只有一个线程的线程池,按先进先出的顺序执行队列中的任务。
newSingleThreadScheduledExecutor()方法:只有一个线程的线程池,但是可以指定执行时间或执行周期 newScheduleThreadPool()方法:同上一个方法,只不过可以指定线程数量。
7.2 自定义线程池

newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool()方法其内部都使用了 ThreadPoolExecutor线程池。

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
我们可以看到,以上线程池都只是 ThreadPoolExecutor 类的封装。这是一个比较复杂的类,我们通过构造函数来慢慢了解它。

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池线程数量
maxMumPoolSize:最大线程数量
keepAliveTime:超过corePoolSize数量的线程的存活时间
unit: keepAliveTime的单位
workQueue:任务队列,保存了被提交但是未被执行的任务
threadFactory:用于创建线程的线程工厂
workQueue用来盛放被提交但未执行的任务队列,它是一个BlockingQueue 接口的对象,仅用于存放Runnable对象。可以使用以下一种阻塞队列直接提交队列:
由SynchronousQueue对象提供。即每一次提交任务时,如果没有空闲的线程,就会提交失败或者执行拒绝策略。因此,此时要设置很大的maximumPoolSize值。
有界的任务队列:可以使用 ArrayBlockingQueue。这时,如果有新任务需要执行,且实际线程数小于corePoolSize 时,会优先创建线程。若大于corePoolSize,则会将任务加入等待队列。若队列已满,则会创建新线程且保证线程数不大于 maximumPoolSize。若大于 maximumPoolSize,则执行拒绝策略。
无界任务队列:使用 LinkedBlockingQueue 类实现。和有界任务队列类似,只不过系统线程数到达corePoolSize后就不在增加。后续任务都会放入阻塞队列中,直到耗尽系统资源
优先任务队列: 通过 PriorityBlockingQueue 实现。可以控制任务的执行先后顺序,是一个特殊的无界队列。
ThreadPoolExecutor 最后一个参数 handler 指定了拒绝策略,有如下几种:

AbortPolicy策略:直接抛出异常,阻止系统正常工作。
CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
DiscardOledestPolicy策略:丢弃最老的一个请求(即将被执行的任务),并尝试再次提交当前任务。
DiscardPolicy策略:丢弃无法处理的任务,不作任何处理。 以上拒绝策略都实现了RejectedExecutionHandler接口,我们也可以扩展这个接口来实现自己的拒绝策略。
8.1 Concurrent。util常用类

1.CyclicBarrier使用: 假设有只有的一个场景:每个线程代表一个怕不运动员,当运动元都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。 实例:

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UseCyclicBarrier {

static class Runner implements Runnable {  
    private CyclicBarrier barrier;  
    private String name;  

    public Runner(CyclicBarrier barrier, String name) {  
        this.barrier = barrier;  
        this.name = name;  
    }  
    @Override  
    public void run() {  
        try {  
            Thread.sleep(1000 * (new Random()).nextInt(5));  
            System.out.println(name + " 准备OK.");  
            barrier.await();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch (BrokenBarrierException e) {  
            e.printStackTrace();  
        }  
        System.out.println(name + " Go!!");  
    }  
} 

public static void main(String[] args) throws IOException, InterruptedException {  
    CyclicBarrier barrier = new CyclicBarrier(3);  // 3 
    ExecutorService executor = Executors.newFixedThreadPool(3);  

    executor.submit(new Thread(new Runner(barrier, "zhangsan")));  
    executor.submit(new Thread(new Runner(barrier, "lisi")));  
    executor.submit(new Thread(new Runner(barrier, "wangwu")));  

    executor.shutdown();  
}  

}
2.CountDownLacth使用: 他经常用于监听某些初始化操作,等待初始化执行完毕后,通知主线程继续工作。

import java.util.concurrent.CountDownLatch;

扫描二维码关注公众号,回复: 2805060 查看本文章

public class UseCountDownLatch {

public static void main(String[] args) {

    final CountDownLatch countDown = new CountDownLatch(2);

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println("进入线程t1" + "等待其他线程处理完成...");
                countDown.await();
                System.out.println("t1线程继续执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    },"t1");

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println("t2线程进行初始化操作...");
                Thread.sleep(3000);
                System.out.println("t2线程初始化完毕,通知t1线程继续...");
                countDown.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    Thread t3 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println("t3线程进行初始化操作...");
                Thread.sleep(4000);
                System.out.println("t3线程初始化完毕,通知t1线程继续...");
                countDown.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    t1.start();
    t2.start();
    t3.start();
}

}
3.Callable和Future
这个例子其实就是Future模式。JDK给予我们一个实现的封装,使用非常简单。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class UseFuture implements Callable{
private String para;

public UseFuture(String para){
    this.para = para;
}

/**
 * 这里是真实的业务逻辑,其执行可能很慢
 */
@Override
public String call() throws Exception {
    //模拟执行耗时
    Thread.sleep(5000);
    String result = this.para + "处理完成";
    return result;
}

//主控制函数
public static void main(String[] args) throws Exception {
    String queryStr = "query";
    //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
    FutureTask<String> future = new FutureTask<String>(new UseFuture(queryStr));

    FutureTask<String> future2 = new FutureTask<String>(new UseFuture(queryStr));
    //创建一个固定线程的线程池且线程数为1,
    ExecutorService executor = Executors.newFixedThreadPool(2);
    //这里提交任务future,则开启线程执行RealData的call()方法执行
    //submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值

    Future f1 = executor.submit(future);        //单独启动一个线程去执行的
    Future f2 = executor.submit(future2);
    System.out.println("请求完毕");

    try {
        //这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
        System.out.println("处理实际的业务逻辑...");
        Thread.sleep(1000);
    } catch (Exception e) {
        e.printStackTrace();
    }
    //调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
    System.out.println("数据:" + future.get());
    System.out.println("数据:" + future2.get());

    executor.shutdown();
}

}
Future模式非常适合在处理很耗时很长的业务逻辑是进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量。

9.1 锁

在java多线程中,我们知道可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个同步互斥工作,他就是lock对象,我们主要学习两种锁,重入锁和读写锁。他们具有比synchronized更为强大的功能,并且有嗅探锁定,多路分支功能。

重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseReentrantLock {

private Lock lock = new ReentrantLock();

public void method1(){
    try {
        lock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
        Thread.sleep(1000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {

        lock.unlock();
    }
}

public void method2(){
    try {
        lock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
        Thread.sleep(2000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {

        lock.unlock();
    }
}

public static void main(String[] args) {

    final UseReentrantLock ur = new UseReentrantLock();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            ur.method1();
            ur.method2();
        }
    }, "t1");

    t1.start();
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //System.out.println(ur.lock.getQueueLength());
}

}
9.2 锁与等待、通知 我们在使用Lock的时候,可以使用一个新的等待,通知的类,他就是Condition,这个Condition一定是针对具体的某一把锁的。也就是在只有锁的基础上才会产生Condition。我们可以通过一个lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。事例:

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

public class UseCondition {

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void method1(){
    try {
        lock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
        Thread.sleep(3000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
        condition.await();  // Object wait
        System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

public void method2(){
    try {
        lock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
        Thread.sleep(3000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
        condition.signal();     //Object notify
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

public static void main(String[] args) {

    final UseCondition uc = new UseCondition();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            uc.method1();
        }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            uc.method2();
        }
    }, "t2");
    t1.start();

    t2.start();
}

}
9.3 ReentrantReadWriteLock(读写锁) 读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁,在高并发访问下,尤其是读多写少的情况下,性能要高于重入锁。其本质是分为两个锁,既读、写锁。在读锁下,多个线程尅并发的进行访问,但是在写锁的时候,只一个一个的顺序访问。
读读共享,写写互斥,读写互斥。事例代码:

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class UseReentrantReadWriteLock {

private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private ReadLock readLock = rwLock.readLock();
private WriteLock writeLock = rwLock.writeLock();

public void read(){
    try {
        readLock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
        Thread.sleep(3000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        readLock.unlock();
    }
}

public void write(){
    try {
        writeLock.lock();
        System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
        Thread.sleep(3000);
        System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        writeLock.unlock();
    }
}

public static void main(String[] args) {

    final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            urrw.read();
        }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            urrw.read();
        }
    }, "t2");
    Thread t3 = new Thread(new Runnable() {
        @Override
        public void run() {
            urrw.write();
        }
    }, "t3");
    Thread t4 = new Thread(new Runnable() {
        @Override
        public void run() {
            urrw.write();
        }
    }, "t4");       

    t1.start();
    t2.start();

    t1.start(); // R 
    t3.start(); // W

    t3.start();
    t4.start();

}

}
9.4锁优化总结

避免死锁
减少锁的持有时间
减少锁的粒度
锁的分离
尽量使用无锁的操作,如原子操作(Atomic系列类)。volatile关键字

猜你喜欢

转载自blog.csdn.net/gchd19921992/article/details/71636940