九.线程的应用

一.多线程概念与优点

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

二.创建线程方式

  • 继承Thread类:
    实现方式:new Thread().start()
  • 继承Runnable接口:
    实现方式:new Thread(Rb对象).start()
  • 继承Callable接口
    • 区别:Callable 接口类似于 Runnable,但是 Runnable 不会返回结果,并且无法抛出经过检查的异常,而它常和future与futuretask何用得到返回值。
    • 实现方式:td(Callable对象)
      FutureTask 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());

三.线程的生命周期

九.线程的应用_第1张图片
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
  • 信号量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

你可能感兴趣的:(九.线程的应用)