九.线程的应用

一.多线程概念与优点

  • 程序,进程与线程
    -都是运行在操作系统之上
    -程序:即一段静态的代码。是为了完成特定任务的一组指令集合
    -进程:即正在运行的程序,是一个动态的过程,有其自身的生命周期
    -线程:进程可细化为线程,是程序内部的执行路径,若有多个线程即为多线程
  • 优点
    发挥多核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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容