美文网首页程序员
九.线程的应用

九.线程的应用

作者: 蜗牛1991 | 来源:发表于2017-09-20 13:55 被阅读0次

    一.多线程概念与优点

    • 程序,进程与线程
      -都是运行在操作系统之上
      -程序:即一段静态的代码。是为了完成特定任务的一组指令集合
      -进程:即正在运行的程序,是一个动态的过程,有其自身的生命周期
      -线程:进程可细化为线程,是程序内部的执行路径,若有多个线程即为多线程
    • 优点
      发挥多核cpu优势,防止阻塞,便于建模(如fork-join框架:采用 “工作窃取”模式(work-stealing))

    二.创建线程方式

    • 继承Thread类:
      实现方式:new Thread().start()
    • 继承Runnable接口:
      实现方式:new Thread(Rb对象).start()
    • 继承Callable接口
      • 区别:Callable 接口类似于 Runnable,但是 Runnable 不会返回结果,并且无法抛出经过检查的异常,而它常和future与futuretask何用得到返回值。
      • 实现方式:td(Callable对象)
        FutureTask<Integer> result = new FutureTask<>(td);
        new Thread(result).start();
        Integer sum = result.get()(获取返回的结果);
    • 线程池创建线程:
      • 实现方式:ExecutorService pool = Executors.newFixedThreadPool(5)
      • //执行传统Runnable任务
        pool .execute(new RunnableTask());
        //执行Callable任务并获得任务结果Future
        Future future = pool.submit(new CallableTask());

    三.线程的生命周期

    image.png

    四 线程同步方式

    • syschronized关键字
      • 加锁方式:
        加入同步代码块: synchronized(锁){...}
        同步方法:synchronized 权限修饰符 返回值类型 方法名(参数){}

      • 锁的对象: 静态方法的同步锁是当前类.class对象,非静态方法的同步锁是当前对象this,同步块的锁就是synchronized()传入的对象

      • 实例:传入同一个类不同对象,线程获取的是不同的锁,本演示消费者A与消费者B为同一个锁(clerk对象),消费者C与消费者D为同一个锁(clerk1对象),加static int product会在各线程共享变量。如果想要所有线程公用一个方法,则在方法加static,此时同一个类不同对象的锁为(clerk.class对象)

    public class SynchronizedTest {
    
            public static void main(String[] args) {
                Clerk clerk = new Clerk();
                Clerk clerk1 = new Clerk();
                Consumer con = new Consumer(clerk);
                Consumer con1 = new Consumer(clerk1);
                new Thread(con, "消费者 A").start();
                new Thread(con, "消费者 B").start();
                new Thread(con1, "消费者 C").start();
                new Thread(con1, "消费者 D").start();
            }
        }
    // 店员
    class Clerk {
        private //*static*// int product = 5;
        // 进货
        public  synchronized void get() { // 循环:0
            while (product > 5) {
                System.out.println("产品已满!");
    
                try {
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName() + " : " + product++);
            this.notifyAll();
        }
    
        // 卖货
        public synchronized void sale() {// product = 0, 循环: 0
            while (product <= 0) { //为了避免虚假唤醒,wait() 应该总是使用在 循环中
                System.out.println("缺货!");
               try {
                    this.wait();// ----
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName() + " : " + product--);
            this.notifyAll();
        }
    }
    
    // 生产者
    class Productor implements Runnable {
        private Clerk clerk;
        public Productor(Clerk clerk) {
            this.clerk = clerk;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                clerk.get();
            }
        }
    }
    // 消费者
    class Consumer implements Runnable {
        private Clerk clerk;
    
        public Consumer(Clerk clerk) {
            super();
            this.clerk = clerk;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                clerk.sale();
            }
        }
    //不加static结果
    产品已满!
    消费者 A : 5
    消费者 B : 4
    消费者 C : 5
    消费者 D : 4
    消费者 A : 3
    消费者 C : 3
    消费者 B : 2
    消费者 D : 2
    消费者 B : 1
    消费者 C : 1
    缺货!
    缺货!
    缺货!
    缺货!
    //加了static结果
    消费者 B : 5
    消费者 C : 4
    消费者 A : 3
    消费者 D : 2
    消费者 A : 0
    消费者 C : 1
    缺货!
    缺货!
    缺货!
    缺货!
    
    • ReentrantLock
      • 加锁方式:
     private Lock lock = new ReentrantLock();
    //线程通信
     private Condition condition2 = lock.newCondition();
     public void loopA(int totalLoop) {
            lock.lock();
              ...
          lock.unLock();
    }
    
    • ReentrantLock与syschronized的区别
      • syschronized是关键字,Lock是类
      • syschronized加锁原理是基于JVM指令,是隐式锁,性能较差且缺乏灵活性(线程要么得到锁,要么阻塞);而ReentrantLock是可重入的显式锁,作用和synchronized关键字类似,但性能、灵活性要更高一点。
      • ReentrantLock可获得Condition接口的实例以便对通信线程进行更灵活的控制
    class AlternateDemo {
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private int number = 1; //用于确定当前执行线程的标记
    
        public void loopA(int totalLoop) {
            lock.lock();
            try {
                //判断
                if (number != 1) {
                    try {
                        condition1.await();
                    } catch (InterruptedException e) {
                    }
                }
                //打印
                for (int i = 1; i <= 1; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
                }
                //唤醒
                number = 2;
                condition2.signal();
            } finally {
                lock.unlock();
            }
        }
    
        public void loopB(int totalLoop) {
            lock.lock();
            try {
                //判断
                if (number != 2) {
                    try {
                        condition2.await();
                    } catch (InterruptedException e) {
                    }
                }
                //打印
                for (int i = 1; i <= 1; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
                }
                //唤醒
                number = 1;
                condition1.signal();
            } finally {
                lock.unlock();
            }
        }
    }
    
    • CountDownLatch闭锁
      • 加锁方式:用给定的计数 初始化 CountDownLatch。由于调用了 countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
        CountDownLatch signal = new CountDownLatch(1);
        signal.await();
        signal.countDown();

      • 示例

    public class CountDownLatchTest {
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch startSignal = new CountDownLatch(1);
            CountDownLatch doneSignal = new CountDownLatch(3);
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            for (int i=0;i<3;i++){
               executorService.execute(new Worker(startSignal, doneSignal));
            }
            System.out.println("准备完毕");
            startSignal.countDown();
            System.out.println("开始执行");
            doneSignal.await();
            System.out.println("执行完毕");
    
        }
         static class Worker implements Runnable {
            private  static Integer count=1;
            private final CountDownLatch startSignal;
            private final CountDownLatch doneSignal;
            Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
                this.startSignal = startSignal;
                this.doneSignal = doneSignal;
            }
            public void run() {
                try {
                    startSignal.await();
                    doWork(count);
                    count++;
                    doneSignal.countDown();
                } catch (InterruptedException ex) {} // return;
            }
    
            void doWork(Integer count) {
                System.out.println(Thread.currentThread().getName()+"-"+count);
            }
        }
    }
    //结果
    准备完毕
    开始执行
    pool-1-thread-1-1
    pool-1-thread-2-2
    pool-1-thread-3-1
    执行完毕
    
    • CyclicBarrier
      • 加锁方式:
        CyclicBarrier barrier = new CyclicBarrier(N, new Runnable() { });//指定所有线程完成后后续任务
        CyclicBarrier barrier = new CyclicBarrier(N);//无后续任务
        cyclicBarrier.await();//各个线程完成等待其他线程完成
      • 与CountDownLatch区别
        1.CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成再执行后续任务。CountDownLatch就像接力赛,只有前三个选手(线程)完成,后一个选手(线程)才能继续任务;CyclicBarrier就像个大坝,所有河流的水的汇聚一起,然后到达预设位置(barrier)时,一起开闸放水。
        2.CyclicBarrier可重用,CountDownLatch是减计数的,而CyclicBarrier是加计数的。
    public class CyclicBarrierTest {
        public static void main(String[] args) {
            int N = 4;
            CyclicBarrier barrier = new CyclicBarrier(N, new Runnable() {
                @Override
                public void run() {
                    System.out.println("所有线程写入完毕,继续处理其他任务...");
                }
            });
    
            for (int i = 0; i < N; i++)
                new Writer(barrier).start();
            try {
                Thread.sleep(2500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("CyclicBarrier重用");
    
            for(int i=0;i<N;i++) {
                new Writer(barrier).start();
            }
        }
    
        static class Writer extends Thread {
            private CyclicBarrier cyclicBarrier;
    
            public Writer(CyclicBarrier cyclicBarrier) {
                this.cyclicBarrier = cyclicBarrier;
            }
    
            @Override
            public void run() {
                try {
                    System.out.println("线程" + Thread.currentThread().getName() + "写入数据完毕,等待其他线程写入完毕");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    //结果
    线程Thread-0写入数据完毕,等待其他线程写入完毕
    线程Thread-1写入数据完毕,等待其他线程写入完毕
    线程Thread-2写入数据完毕,等待其他线程写入完毕
    线程Thread-3写入数据完毕,等待其他线程写入完毕
    所有线程写入完毕,继续处理其他任务...
    CyclicBarrier重用
    线程Thread-4写入数据完毕,等待其他线程写入完毕
    线程Thread-5写入数据完毕,等待其他线程写入完毕
    线程Thread-6写入数据完毕,等待其他线程写入完毕
    线程Thread-7写入数据完毕,等待其他线程写入完毕
    所有线程写入完毕,继续处理其他任务...
    
    • 信号量Semaphore
      • 设置最大线程数(一般使用公平模式):private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
        线程获取一个资源:available.acquire();(剩余许可的线程数-1)
        一个线程释放资源:available.release();(剩余许可的线程数+1)
    • Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目
    public class SemaphoreTest {
        public static void main(String[] args) throws InterruptedException {
            ExecutorService executorService = Executors.newFixedThreadPool(6);
            ContentPool contentPool = new ContentPool();
            System.out.println("总剩余资源为6,每次只能允许4个线程获得资源,当线程资源释放时,其他线程才可以继续获得资源");
            for (int i = 0; i < 6; i++) {
                executorService.execute(new user(contentPool));
            }
        }
    }
    
    class ContentPool {
        private static final int MAX_AVAILABLE = 4;
        private static int count = 6;
        private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
    
        public Object getItem() throws InterruptedException {
            available.acquire();
            --count;
            System.out.println("占用一个资源,剩余资源为:" + count);
            return getNextAvailableItem();
        }
    
        public void putItem(Object x) {
            if (markAsUnused(x))
                available.release();
            ++count;
            System.out.println("释放一个资源,剩余资源为:" + count);
        }
    
        protected int[] items = {1, 2, 3, 4,5,6};
        protected boolean[] used = new boolean[MAX_AVAILABLE];
    
        synchronized Object getNextAvailableItem() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (!used[i]) {
                    used[i] = true;
                    return items[i];
                }
            }
            return null; // not reached
        }
    
        synchronized boolean markAsUnused(Object item) {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (item == items[i]) {
                    if (used[i]) {
                        used[i] = false;
                        return true;
                    } else
                        return false;
                }
            }
            return false;
        }
    }
    
    class user implements Runnable {
        private ContentPool contentPool;
        private int count = (int) (Math.random() * 5+ 1);
    
        public user(ContentPool contentPool) {
            this.contentPool = contentPool;
        }
    
        @Override
        public void run() {
            try {
                Object item = contentPool.getItem();
                System.out.println(Thread.currentThread().getName()+"得到资源"+item);
                Thread.sleep(1000);
                contentPool.putItem(count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //结果
    总剩余资源为6,每次只能允许4个线程获得资源,当线程资源释放时,其他线程才可以继续获得资源
    占用一个资源,剩余资源为:5
    pool-1-thread-2得到资源1
    占用一个资源,剩余资源为:4
    占用一个资源,剩余资源为:3
    pool-1-thread-6得到资源2
    pool-1-thread-1得到资源3
    占用一个资源,剩余资源为:2
    pool-1-thread-5得到资源4
    释放一个资源,剩余资源为:3
    占用一个资源,剩余资源为:2
    pool-1-thread-3得到资源3
    释放一个资源,剩余资源为:4
    释放一个资源,剩余资源为:5
    释放一个资源,剩余资源为:6
    占用一个资源,剩余资源为:3
    pool-1-thread-4得到资源1
    释放一个资源,剩余资源为:5
    释放一个资源,剩余资源为:6
    

    相关文章

      网友评论

        本文标题:九.线程的应用

        本文链接:https://www.haomeiwen.com/subject/gzgwsxtx.html