黑马程序员_Java基础_线程池

来源:互联网 发布:sketch是什么软件 编辑:程序博客网 时间:2024/05/16 05:04

------- android培训、java培训、期待与您交流! ----------

首先,我们先了解一下什么是线程池?又什么时候使用到线程池技术?


 我们生活中很多事情的处理和线程池有很大的关系,比如:火车售票,银行系统等
这些行业内部的高性能服务就是使用到了线程池技术,并且得到安全和高效的保障!

 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
 
那什么时候使用到线程池技术呢? 
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。  
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

 
一个线程池包括以下四个基本组成部分:
 
1、线程池管理器(ThreadPool):用于创建、销毁并管理线程池,将工作线程放入线程池中。
 
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务
 
3、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
 
4、 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,
它主要规定了任务的入口,任务执行完后的收尾工作,
任务的执行状态等;

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。
它把
T1T3分别安排在服务器程序的启动和结束的时间段或者一
些空闲的时间段,
这样在服务器程序处理客户请求时,不会有T1T3的开销了。


下面我们就逐一去了解有关java线程池使用到的类,及一些相关应用 
 

 例子1:
我们先了解一下线程中的定时器:
这种可以使用定时查收邮件的,或者发送邮件(实用):  quartz(开源项目)
还有游戏制作的时候可以使用到定时技术。

 下面使用线程的定时技术制作一个定时炸弹:代码如下

public class TraditionalTimer {    static int count = 0;    public static void main(String[] args) {        // method1();        // method2();//      method3();        method4();    }    public static void method4() {        Timer t = new Timer();        t.schedule(new MyTimerTask2(), 2000);        timecount();    }    /**     * 方法功能:每隔两秒就爆炸一次     */    public static void method3() {        Timer t = new Timer();        t.schedule(new MyTimerTask(true), 3000);        timecount();    }    /**     * 方法功能:每隔两秒就爆炸一次     */    public static void method2() {        Timer t = new Timer();        t.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("爆炸");            }        }, 2000, 2000);        timecount();    }    /**     * 方法功能:定时炸弹2秒钟之后爆炸     */    public static void method1() {        Timer t = new Timer();        t.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("爆炸");            }        }, 2000);        timecount();    }    /**     * 方法功能:输出时间计时     */    public static void timecount() {        new Thread() {            public void run() {                while (true) {                    try {                        System.out.println(new Date().getSeconds());                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            };        }.start();    }}// 第一种实现方式:自定义一个定时的任务,间接性的经过2秒钟之后爆炸一次,有经过四秒钟之后爆炸一次,,一次循环class MyTimerTask extends TimerTask {    private boolean flag = true;    MyTimerTask(boolean flag) {        this.flag = flag;    }    @Override    public void run() {        System.out.println("爆炸");        new Timer().schedule(new MyTimerTask(flag ? false : true), flag ? 2000                : 4000);    }}// 第二种实现方式:class MyTimerTask2 extends TimerTask {    private  static int count = 0;     @Override    public void run() {        this.count = (this.count + 1) % 2;        System.out.println("爆炸");        new Timer().schedule(new MyTimerTask2(), 2000 + 2000 * this.count);    }}

需求:用面试宝典中的子线程循环10次和主线程循环5次,两者交替运行50的例子
 
  为了观察方便,我们可以将运行的结果通过重定向到一个文本文件中,在 run configuration中配置
例子2:


public class TraditionalThreadTest {    public static void main(String[] args) {        final MyThread t = new MyThread();        new Thread(new Runnable() {            @Override            public void run() {                for (int j = 1; j <= 50; j++) {                    t.sub(j);                }            }        }).start();        for (int j = 1; j <= 50; j++) {            t.main(j);        }    }}class MyThread {    private boolean flag = true;    public synchronized void sub(int j) {        while (flag) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        for (int i = 1; i <= 10; i++) {            System.out.println("sub-------------->" + i + " : " + j);        }        this.flag = true;        this.notify();    }    public synchronized void main(int j) {        while (!flag) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        for (int i = 1; i <= 100; i++) {            System.out.println("main--->" + i + " : " + j);        }        this.notify();        flag = false;    }}

需求:同一线程必须操作同一对象的元素,不然会出现安全问题
一个线程使用一个独立的对象数据,不能使用其他线程对象的数据 
ThreadLocal该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联我们通过例子来测试:
例子3: 
 使用两个线程,并将两个数据分别放入到ThreadLocal类中,

这个类能保证线程之间的数据不会干扰 

public class ThreadScopeTest {//    private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();    private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();    public static void main(String[] args) {        for (int i = 0; i < 2; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    Random r = new Random();                    int data = r.nextInt();                     System.out.println(Thread.currentThread().getName() + " creat: "                     + data);//                  map.put(Thread.currentThread(), data);                    x.set(data);                    new A().print();                    new B().print();                }            }).start();        }    }    static class A {        public void print() {//          int data = map.get(Thread.currentThread());            int data = x.get();            System.out.println("A:" + Thread.currentThread().getName() + ":"                    + data);        }    }    static class B {        public void print() {//          int data = map.get(Thread.currentThread());            int data = x.get();            System.out.println("B:" + Thread.currentThread().getName() + ":"                    + data);        }    }}

 输出结果:A:Thread-0:-704470354 A:Thread-1:-795936612 B:Thread-1:-795936612
 B:Thread-0:-704470354
 
 结论:同一线程使用的是自己的数据,不能使用其他线程的数据

需求:测试同一线程在执行过程中,自己使用的对象不会被其他线程使用,他们之间是互斥的
 
public class ThreadLocalTest {    private static ThreadLocal<MyTreadScopeData> map = new ThreadLocal<MyTreadScopeData>();    public static void main(String[] args) {        for (int i = 0; i < 2; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    Random r = new Random();                    int data = r.nextInt();                    MyTreadScopeData my = MyTreadScopeData.getInstance();                    my.setAge(data);                    my.setName("name" + data);                    new A().print();                    new B().print();                }            }).start();        }    }    static class A {        public void print() {            MyTreadScopeData data = MyTreadScopeData.getInstance();            System.out.println("A:" + Thread.currentThread().getName() + ":"                    + data);        }    }    static class B {        public void print() {            MyTreadScopeData data = MyTreadScopeData.getInstance();            System.out.println("B:" + Thread.currentThread().getName() + ":"                    + data);        }    }}class MyTreadScopeData {    private static ThreadLocal<MyTreadScopeData> map = new ThreadLocal<MyTreadScopeData>();    private MyTreadScopeData() {    }    public static MyTreadScopeData getInstance() {        MyTreadScopeData instance = map.get();        if (instance == null) {            instance = new MyTreadScopeData();            map.set(instance);        }        return instance;    }    private String name;    private int age;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return " [name=" + name + ", age=" + age + "]";    }}
 Executor类的应用:(用于创建线程池)

 (在线程执行完任务之后,线程是不会消亡的)可以使用ExecutorService.shutdown()将线程池中的线程关闭
 
 使用线程池技术,是为了减少线程在创建和销毁的过程中所浪费的资源和时间
 
线程池中的线程没有接收到任务的时候,线程将出入wait状态,当有任务进入时,将唤醒notify线程
 
  tomcat服务器就是使用线程池的原理
我们可以使用这个方法将线程池中的线程进行消亡(默认是不会关闭线程的)
 ThreadPool.shutdown();


 使用Executor的静态newFixedThreadPool()方法创建线程池,并 指定线程数量
  使用ExecutorService类接受线程池
   ExecutorService是固定的线程池
   ExecutorService ThreadPool = Executors.newFixedThreadPool(3);

 使用线程池的缓冲技术,线程的数量会顺着任务的多少而改变,
   当任务多的时候.可以动态的创建线程的数量来完成任务,
 ExecutorService ThreadPool = Executors.newCachedThreadPool();

创建一个单一线程,当线程池中的单一线程死亡之后,会自动创建一个新的线程保证线程池中有唯一一个线程
ExecutorService ThreadPool = Executors.newSingleThreadExecutor();

 例子:创建缓冲线程池,会动态创建线程,一般线程数量和任务数量相等

public class ThreadPoolTest {    public static void main(String[] args) {        ExecutorService ThreadPool = Executors.newCachedThreadPool();        // 往线程池中添加10个任务        for (int i = 0; i <= 10; i++) {            final int task = i;            ThreadPool.execute(new Runnable() {                @Override                public void run() {                    for (int j = 0; j <= 10; j++) {                        try {                            Thread.sleep(20); // 让线程休眠一会                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                        System.out.println(Thread.currentThread().getName()                                + " loop of " + j + "for task " + task);                    }                }                private void extracted() throws InterruptedException {                    Thread.sleep(20);                }            });        }        System.out.println("添加完成十个任务");        ThreadPool.shutdownNow();    }}

 Lock类比传统线程模型中的sychronized方式更加面向对象,
 实现允许更灵活的结构,可以具有差别很大的属性
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。 
  
  与生活中的锁类似,锁本身也应该是一个对象,2个线程执行的代码片段要实现同步互斥的效果,
  他们必须用同一个Lock对象,
  
 读写锁:分为读锁和写锁,多个读硕不互斥,读锁与写锁互斥,写锁和写锁互斥,
 这是有JVM自己控制的,你只要上好相应的锁即可,
 如果你代码只读数据,可以很多人同时读,但是不能同时写,那就使用读锁;
 如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁
 总之,读的时候上读硕,写的时候上写锁 
例子:
public class LockTest {    public static void main(String[] args) {        final Add add1 = new Add();        // 创建2个线程进行打印名字        new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    add1.add("---------");                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    add1.add("++++++++++");                }            }        }).start();    }}class Add {    Lock lock = new ReentrantLock();    public void add(String name) {        // 枷锁        lock.lock();        try {            for (int i = 0; i < name.length(); i++) {                System.out.print(name.charAt(i));            }            System.out.println();        } finally {            lock.unlock(); // 使用finally捕捉,使得代码更加安全        }    }}

ReadWriteLock 维护了一对相关的,一个用于只读操作,另一个用于写入操作。


只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。

一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。

不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
 
例子: 需求:模拟Hibernate获取数据库信息

public class CacheTest {    private Map<String, Object> cache = new HashMap<String, Object>();    public static void main(String[] args) {    }    private ReadWriteLock rwl = new ReentrantReadWriteLock();    public Object getData(String key) {        // 上读锁        rwl.readLock().lock();        Object value = null;        try {            value = cache.get(key);            if (value == null) {                rwl.readLock().unlock();                rwl.writeLock().lock();                try {                    if (value == null) {                        value = "aaaa"; // 模拟数据库获取数据,当内存中没有资源时,到数据库中获取                    }                } finally {                    rwl.writeLock().unlock();                }                rwl.readLock().lock();            }        } finally {            rwl.readLock().unlock(); // 解锁        }        return value;    }}

例子:需求:制作一个定时器,程序运行6秒后,爆炸一次,以后每隔2秒爆炸一次


public class ExecutorTest {    public static void main(String[] args) {        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {            @Override            public void run() {                System.out.println("爆炸");            }        }, 6, 2, TimeUnit.SECONDS);    }}

Condition 类
 
将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
 以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
 其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用

 Condition 实例实质上被绑定到一个锁上。
 要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法 

注意,Condition 实例只是一些普通的对象,它们自身可以用作 synchronized 语句中的目标,
并且可以调用自己的 wait 和 notification 监视器方法。获取 Condition 实例的监视器锁或者使用其监视器方法,
与获取和该 Condition 相关的 Lock 或使用其 waiting 和 signalling 方法没有什么特定的关系。
为了避免混淆,建议除了在其自身的实现中之外,切勿以这种方式使用 Condition 实例。  
 
例子:
 
需求:有三个线程,主线程,二线程,三线程,它们依次排序执行循环十次(主线程----> 二 线程---->三线程) 
public class ThreadConditionCommunication {    public static void main(String[] args) {        final ThreadQueue t = new ThreadQueue();        // 创建线程2        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i < 50; i++) {                    t.subThread2(i);                }            }        }).start();        // 创建线程3        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i < 50; i++) {                    t.subThread3(i);                }            }        }).start();        //主线程执行的部分        for (int i = 0; i < 50; i++) {            t.main(i);        }    }}class ThreadQueue {    Lock lock = new ReentrantLock();    Condition condition1 = lock.newCondition();    Condition condition2 = lock.newCondition();    Condition condition3 = lock.newCondition();    private int queue = 1;    public void subThread2(int j) {        lock.lock();        try {            while (queue != 2)                condition2.await();            for (int i = 0; i < 10; i++) {                System.out.println("sub2 ---------->" + i + ":" + j);            }            queue = 3;            condition3.signal();        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void subThread3(int j) {        lock.lock();        try {            while (queue != 3)                condition3.await();            for (int i = 0; i < 10; i++) {                System.out.println("sub3---------->" + i + ":" + j);            }            queue = 1;            condition1.signal();        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void main(int j) {        lock.lock();        try {            while (queue != 1)                condition1.await();            for (int i = 0; i < 10; i++) {                System.out.println("main----------->" + i + ":" + j);            }            queue = 2;            condition2.signal();        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }}

例子:
需求:定义一个工厂 ,工厂容量为100; 
 使用三个线程生产 ,三个线程消费
 
 没有产品的使用不能消费 


public class InOutFactory {    public static void main(String[] args) throws Exception {        final Factory factory = new Factory();        for (int i = 0; i < 3; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    while (true) {                        Random r = new Random();                        factory.put(r.nextInt());                    }                }            }).start();        }        for (int i = 0; i < 3; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    while (true) {                        try {                            System.out.println(Thread.currentThread().getName()                                    + "消费:" + factory.take());                        } catch (InterruptedException e) {                            System.out.println("消费失败");                        }                    }                }            }            ).start();        }    }}class Factory {    Lock lock = new ReentrantLock();    final Condition noFull = lock.newCondition();    final Condition noEmpty = lock.newCondition();    private Object[] items = new Object[100];    int putptr, takeptr, count;    /**     * 生产方法     */    public void put(Object x) {        lock.lock();        try {            while (count == items.length)                // 当生产的数量大于一百的时候停止生产                noFull.await();            items[putptr] = x;            if (++putptr == items.length)                putptr = 0;            count++;            System.out.println(Thread.currentThread().getName() + "生产:" + x);            Thread.sleep(1000);            noEmpty.signal();        } catch (Exception e) {            System.out.println("生产出现异常");        } finally {            lock.unlock();        }    }    /**     * 消费的方法     *      */    public Object take() throws InterruptedException {        lock.lock();        try {            while (count == 0) {                noEmpty.await();            }            Object x = items[takeptr];            if (++takeptr == items.length)                takeptr = 0;            --count;            noFull.signal();            return x;        } finally {            lock.unlock();        }    }}

Semaphore类实现信号灯 

Semaphore可以维护当前访问自身的线程个数,并提供同步线程机制。 
  
  例如:实现一个文件允许的并发访问数
  
  Semaphore实现的功能类似厕所有5个坑,假如有10个人上厕所,那么同时只能有5个人能够占用,
  当5个人中的任何一个人让开后,其中在等待的另外5个人中有一个可以占用了
 
 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁” 再由另一个线程放“锁”,
 这可应用于死锁恢复的一些场合 

一个计数信号量。从概念上讲,信号量维护了一个许可集。

如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。

每个release() 添加一个许可,从而可能释放一个正在阻塞的获取者。

但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。 

例子: 
需求:有资源数目为3,多个线程排队使用资源,只能有三个线程同时使用资源


public class SemaphoreTest {    public static void main(String[] args) {                //创建线程缓冲池,有多少任务将创建对应数量的线程个数        ExecutorService ThreadPool = Executors.newCachedThreadPool();                //定义资源的数量为3个        final Semaphore sp = new Semaphore(3);                for (int i = 0; i < 10; i++) {            Runnable runnable = new Runnable() {                @Override                public void run() {                    try {                        //从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断                        sp.acquire();                       } catch (InterruptedException e) {                        e.printStackTrace();                    }                                        //availablePermits()返回此信号量中当前可用的许可数                    System.out.println("线程" + Thread.currentThread().getName()                            + "进入,当前已有" + (3 - sp.availablePermits()));                    try {                        Thread.sleep((long) (Math.random() * 10000));                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println("线程" + Thread.currentThread().getName()                            + "即将离开");                    //  释放一个许可,将其返回给信号量。                    sp.release();                    System.out.println("线程" + Thread.currentThread().getName()                            + "已离开,当前已有" + (3 - sp.availablePermits()));                }            };            ThreadPool.execute(runnable);        }    }}

Future类用于接受线程返回结果; 

Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 
get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
取消则由 
cancel 方法来执行。
还提供了其他方法,以确定任务是正常完成还是被取消了。
一旦计算完成,就不能再取消计算。
如果为了可取消性而使用 
Future 但又不提供可用的结果,
则可以声明 
Future<?> 形式类型、并返回 null 作为底层任务的结果。 


 它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
 计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。

public class CallableAndFuture {    public static void main(String[] args) {        ExecutorService  threadPool = Executors.newSingleThreadExecutor();        Future<String> futuer = threadPool.submit(new Callable<String>(){            @Override            public String call() throws Exception {                Thread.sleep(2000);                return "hello";            }                    });        System.out.println("等待线程返回的结果:");        try {            //等待线程返回的结果//          System.out.println("线程返回的结果:" + futuer.get());            //Futuer.get(1,TimeUnit.SECONDS)指定1秒内接收不到数据,就报出异常            System.out.println("线程返回的结果:" + futuer.get(2,TimeUnit.SECONDS));        } catch (Exception e) {            e.printStackTrace();        }     }}

 CompletionService类
   将生产新的异步任务与使用已完成任务的结果分离开来的服务。 
  生产者 submit 执行的任务。 使用者 take
  已完成的任务,并按照完成这些任务的顺序处理它们的结果。

例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

通常,CompletionService 依赖于一个单独的 Executor 来实际执行任务,在这种情况下,CompletionService 只管理一个内部完成队列。ExecutorCompletionService 类提供了此方法的一个实现。
例子: 
 
public class CompletionServiceTest {    public static void main(String[] args) {        //创建线程池,并往线程池中添加十个线程        ExecutorService threadPool = Executors.newFixedThreadPool(10);        CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(                threadPool);        //向线程中添加十个任务        for (int i = 0; i < 10; i++) {            final int temp = i;            completionService.submit(new Callable<Integer>() {                @Override                public Integer call() throws Exception {                    //随机睡眠时间                    Thread.sleep(new Random().nextInt(5000));                    return temp;                }            });        }        for (int i = 0; i < 10; i++) {            try {                System.out.println(completionService.take().get());            } catch (Exception e) {                e.printStackTrace();            }        }    }}


0 0
原创粉丝点击