精华内容
下载资源
问答
  • 多线程的实现和使用场景

    万次阅读 2021-06-09 22:06:20
    多线程使用场景1.1 多线程应该最多的场景:1.2多线程常见应用场景:2.多线程小案列2.1 多线程计算2.2 多线程实现卖票小程序2.3多线程卖票小程序优化2.4多线程卖票小程序优化升级总结 一、多线程实现方式 1.1 ...

    一、多线程实现方式

    1.1 Thread实现

    继承Thread类并重写它的run方法。之后创建这个子类的对象并调用start()方法。下面直接上代码:

    /**
    *描述
    * @author cy
    * @date 2021-06-09
    * @return
    **/
    public class TreadTest  extends  Thread{
        public static void main(String[] args) {
            //创建两个线程
            TreadTest thread1 = new TreadTest();
            TreadTest thread2 = new TreadTest();
            thread1.start();
            thread2.start();
        }
        @Override
        public void run() {
           for (int i = 0;i < 100;i++){
               //分别打印线程名称和i
               System.out.println("threadName:"
               +Thread.currentThread()+";i="+i);
           }
        }
    }
    

    输出结果可以看到两个线程交替打印。线程启动成功。
    在这里插入图片描述

    1.2 Runnable实现

    Runnable的实现方式是实现其接口,下面请看具体的实现代码

    
    /**
     * @author  cy
     * @date  2021-06-09
     *@deprecated  Runnable实现多线程
     */
    public class RunnableDemoTest {
    
        public static void main(String[] args) {
            //新建两个线程
            RunnableDemoTest1 r =  
            new RunnableDemoTest1();
            new Thread(r).start();
            new  Thread(r).start();
        }
    
        /**
         * 通过runnable 方式实现多线程
          */
    static class  RunnableDemoTest1  
                       implements  Runnable{
        @Override
        public void run() {
            for (int i = 0 ;i < 5 ;i++)
                System.out.println("threadName:"
                +Thread.currentThread()+";i="+i);
        }
    }
    }
    
    

    从输出结果中我们可以看到两个线程交替打印。线程启动成功。
    在这里插入图片描述

    二、多线程的使用场景

    1.多线程使用场景

    1.1 多线程应该最多的场景:

    web服务器本身; 各种专用服务器(如游戏服务器);

    1.2多线程的常见应用场景:

    1、后台任务,例如:定时向大量(100w以上)的用户发送邮件; 2、异步处理,例如:发微博、记录日志等; 3、分布式计算

    2.多线程小案列

    2.1 多线程计算

    计算一亿个数字之和,在没有学习多线程之前,也是可以实现的,我们可以通过循环一亿次进行累加,最后得出结果。
    代码如下:

    package trhead;
    
    import java.util.Random;
    
    /**
     * 计算一亿个数之和
     */
    public class SumThreadDemo {
    
        private static double [] nums = new double[1_0000_0000];
        private static Random r  =  new Random();
        private static  double  result = 0.0,result1 = 0.0,result2 = 0.0;
    
        static {
            for (int i =0;i < nums.length ; i++){
                nums[i] = r.nextDouble();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            SumThreadDemo.m1();
            SumThreadDemo.m2();
        }
    
        /**
         * 使用单线程计算
         */
        private  static  void m1(){
            long startTime = System.currentTimeMillis();
            for (int i = 0;i < nums.length;i++){
                result += nums[i];
            }
            System.out.println(result);
            long endTime = System.currentTimeMillis();
            long countTime = endTime - startTime;
            System.out.println("m1耗时:"+countTime);
        }
    
        /**
         * 使用多线程计算
         */
        private  static  void m2() throws InterruptedException {
    
    
            Thread thread1 =   new Thread(()->{
                for (int i = 0;i < nums.length/2;i++){
                    result1 += nums[i];
                }
            });
    
            Thread thread2 =   new Thread(()->{
                for (int i = nums.length/2;i < nums.length;i++){
                    result2 += nums[i];
                }
            });
            long startTime = System.currentTimeMillis();
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println(result1+ result2);
            long endTime = System.currentTimeMillis();
            long countTime = endTime - startTime;
            System.out.println("m2耗时:"+countTime);
        }
    
    
    
    }
    
    

    从输出结果中可以观察出,两个线程计算结果比单个线程快了将近两倍。如果实际应用中有这种场景,大家可以使用多线程实现。
    在这里插入图片描述

    2.2 多线程实现卖票小程序

    某电影院正在上映某大片,共5张票,而他又有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    多线程实现方式1:

    package trhead;
    
    /**
    *描述
     *  某电影院正在上映某大片,共5张票,
     * 而他又有3个售票窗口售票,请设计一个程序模拟该电影院售票。
    * @author cy
    * @date 2021-06-09
    * @return
    **/
    public class TicketTreadTest extends  Thread{
        //设置票数
        private  int ticket = 5;
        public static void main(String[] args) {
            //创建两个线程
            TicketTreadTest thread1 
                  = new TicketTreadTest();
            TicketTreadTest thread2 
                  = new TicketTreadTest();
            TicketTreadTest thread3 
                  = new TicketTreadTest();
            thread1.start();
            thread2.start();
            thread3.start();
        }
        @Override
        public void run() {
            while(true){
               //分别打印线程名称 和 ticket 数
               System.out.println("threadName:"
               +Thread.currentThread()+";
               ticket="+ticket--);
               if(ticket < 0){
                   break;
                }
           }
        }
    }
    

    结果分析:从下图输出结果中可以分析,使用继承Thread类实现卖票,导致每个窗口都卖了五张票,而这个电影院总共才五张票,多线程出现了超卖现象。原因是继承Thread方式,是多线程多实例,无法实现资源的共享
    在这里插入图片描述

    2.3多线程卖票小程序优化

    在2.2的小程序案列中,我们发现了在多线程的环境下, 由于公共资源可能会被多个线程共享, 也就是多个线程可能会操作同一资源. 当多个线程操作同一块资源时, 很容易导致数据错乱或发生数据安全问题,
    即: 数据有可能丢失, 有可能增加, 有可能错乱.

    我们如何避免这种现象呢?具体看代码:

    package trhead;
    
    /**
     * @author  cy
     * @date  2021-06-09
     *@deprecated  Runnable实现多线程卖票
     */
    public class TicketRunnableDemoTest {
        //设置票数
        private  static int ticket = 5;
    
        public static void main(String[] args) {
            //新建两个线程
            RunnableDemoTest1 r 
                   =  new RunnableDemoTest1();
            new Thread(r).start();
            new  Thread(r).start();
        }
    
        /**
         * 通过runnable 方式实现多线程
          */
    static class  RunnableDemoTest1  
                        implements  Runnable{
        @Override
        public void run() {
            while (ticket > 0){
                saleTicket();
            }
        }
    
            /**
             * 实现卖票方法
             */
        public  void  saleTicket(){
            if(ticket>0){
                System.out.println("threadName:"
                +Thread.currentThread()
                + "正在出票,余票剩余:"+ticket-- +"张");
            }
        }
    }
    }
    
    

    结果分析:从下图输出结果中可以分析,实现Runnable接口进行卖票,电影院总共才五张票,多线程卖票正常。原因是实现Runnable方式,是多线程单实例,可实现资源的共享

    在这里插入图片描述

    2.4多线程卖票小程序优化升级

    细心的小伙伴可能会发现,怎么在2.3输出打印的票数不是从大到小排序的,这跟现实中的卖票场景不一样呐。如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只有一个线程进行,其他线程要等待此线程完成之后才可以继续执行
    下面我们通过对2.3代码进行继续优化,实现真实卖票场景。

     /**
    * 通过synchronized实现线程同步
      * 实现卖票方法
      */ public synchronized   void   saleTicket1(){
          if(ticket>0){
              System.out.println("threadName:"
              +Thread.currentThread()
                      + "正在出票,余票剩余:"
                      + +ticket-- +"张");
          }
      }
    

    结果分析:从下图输出结果中可以分析,通过同步代码的方法进行代码的加锁操作,实现了卖票场景。
    在这里插入图片描述


    总结

    这节主要给大家介绍了多线程的实现以及相应的一些使用场景,并且引入了同步的知识点,下一节主要介绍synchronized关键字的使用。
    另外,码字不容易,如果发现小编描述有误,麻烦指摘。

    展开全文
  • 开发一个稍微复杂的程序就免不了启动一个或者个工作线程。 还有一些线程需要一直运行,接收任务和处理任务。线程的大致结构是: void ThreadProc(){ while(true){ Task t* = GetTask(); t->doTask(); ...

    开发一个稍微复杂的程序就免不了启动一个或者多个工作线程。

    还有一些线程需要一直运行,接收任务和处理任务。线程的大致结构是:

    void ThreadProc(){
        while(true){
            Task t* = GetTask();
            t->doTask();
            Sleep(1000);
        }
    }

    新手在线这样的任务线程时容易犯的错误是线程难以销毁,难以退出。

    于是我们想到可以设置运行标志forceQuit,当forceQuit等于1的时候退出线程。比如:

    void ThreadProc(){
        forceQuit = 0;
        while(!forceQuit){
            Task t* = GetTask();
            t->doTask();
            Sleep(1000);
        }
    }

    这个时候线程时可以退出的,将全局变量forceQuit置1就可以了,但是有时候需要等待很久线程才能退出。

    那是因为Sleep(1000) 是需要等待1秒钟。如果运气不好就需要等待整整1秒钟才能退出线程。

    如果我们需要线程马上响应我们的退出指令怎么办?

    这个时候我们就需要学习和使用到线程同步的知识了。

    线程同步方式有3种。

    互斥(Mutex)、信号量(Semaphore)、事件(Event)。

    我们可以使用线程事件的同步方式使线程马上停止。

    int forceQuit = 1;
    HANDLE hEvent = NULL;
    
    void Stop(){
        forceQuit = 1;
        if(hEvent){
            SetEvent(hEvent);
        }
        
        //pthread_join wait thread exit
    }
    
    void Notify(Task*t){
        AddTask(t);
        if(hEvent){
            SetEvent(hEvent);
        }
    }
    
    void ThreadProc(){
        hEvent = CreateEvent(0,0,0,0);
        forceQuit = 0;
        while(!forceQuit){
              DWORD ret = WaitSingleObject(hEvent,1000);
               Task*t = GetTask();
               t->doTask();
        }
        
    }

    以上伪代码是一个比较通用的线程模型,当我们需要线程马上响应处理的时候可以使用这个代码模型。包括发送任务到线程进行处理优化性能。

    为什么我们需要将任务投递到线程处理?

    我们有时候会编写和引用一些非线程安全的SDK和第三方库。如果这个库需要被多个线程使用的话。假设在 线程1初始化,然后线程2调用就很容易出现代码段错误。访问冲突,或者访问到已经释放的资源。甚至可能开始访问还可以使用,访问到一半被释放了这样的情况。这样的程序极其不稳定。

    所以我们可以将任务投递到初始化的线程处理。这样就不容易出现并行运行中的崩溃问题。

    最近重温了生产者消费者模型和实现,发现这其实就是其实就是生产者消费者的模型变种。多线程编程一定要学好生产者消费者模型。很多情况下,只需要编写多个生产者线程一个消费者线程。生产者是不定期产生信号,需要消费者及时响应处理

     

     

    展开全文
  • 常见多线程问题

    2020-05-15 15:09:52
    多线程提高了程序的使用率,多进程提高了CPU的使用率 区别: 1.线程是进程的子集,一个进程可以有很多线程,每条线程执行不同的任务 2.不同的进程使用不同的内存空间,而所有线程共享一片进程的内存区域(注意不要和...

    一.什么是进程?什么是线程?两者之间的区别?

    进程是拥有一定功能的关于某个数据集合上的一次运动,是系统进行资源分配和调度的一个单位

    线程是进程的实际运作单位,是一个进程中的执行场景,是CPU调度的最小单位

    举个例子,使用谷歌浏览器,谷歌浏览器就是一个进程,而很多人用它上网,就是很多个线程

    多线程提高了程序的使用率,多进程提高了CPU的使用率

    区别:

    1.线程是进程的子集,一个进程可以有很多线程,每条线程执行不同的任务

    2.不同的进程使用不同的内存空间,而所有线程共享一片进程的内存区域(注意不要和栈搞混,每个线程都拥有单独的栈内存用来存储本地数据)

    3.一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃掉整个进程都死掉,所以多进程比多线程健壮。但是进程切换时,消耗 的资源大,效率高,所以涉及到频繁的切换时,使用线程好于进程。如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程

    二.Java中如何实现多线程?

    1.继承Thread类,并重写里面的run()方法

    2.实现Runnable接口,重写run()方法,作为参数传给Thread(更灵活,且可以多继承)

    3.实现Callable接口,重写call()方法,通过FutureTask包装器来创建Thread线程(可以抛出其他异常且可以获得返回值)

    4.定时器,可以定时的来执行某个任务

    创建Timer对象,调用schedule方法,将TimeTask对象作为参数传入方法中,然后重写run()方法

    5.基于线程池的方式。使用Executors工具类创建线程池,execute()方法提交任务,用Runnable作为参数,重写run()方法

    三.start()和run()的区别?

    start()方法用于启动线程,且内部调用了run()方法,真正实现了多线程

    run()称为线程体,它包含了要执行的这个线程的内容,run()运行结束,此线程终止,然后CPU再调度其它线程。如果不使用start(),那么程序仍然会执行,只是被当做普通方法了,这样就没有达到多线程的目的

    四.volatile是什么?

    volatile关键字是一个特殊的类型修饰符,只有成员变量才可以使用它。能确保本条指令不会因为编译器的优化而省略

    在A线程内,当读取一个变量时,为了提高存取速度,编译器会把变量存到高速缓存区当中,以后再取变量值时,就直接从高速缓存区里面取,当变量在线程B中的值改变了,但是线程A中的高速缓存区并不知道,所以的值仍然不变,这样就会造成程序读取的数据和实际的变量不一致

    而volatile的出现就保证了数据的可见性,编译器就对访问该变量的代码不再进行优化,从而达到稳定访问

    五.线程的生命周期?

    在这里插入图片描述
    线程进入就绪状态之后,就有时间去抢夺CPU时间片,这个时间片就是执行权,抢到之后就进入了运行状态奶,当时间用完但是运行还没有结束时,就会又返回到就绪状态,继续抢夺时间片,然后继续未执行完的run(),直至方法执行完毕

    六.wait()

    在这里插入图片描述
    但是需要注意,wait()是Object的方法,所以调用时obj对象进行调用,且要在锁内,如果没有获得对象,也就没有资格去调用,而且,使用wait()方法可能会抛出InterruptedException

    public class WaitTest {
        static Object obj = new Object();
        public static void main(String[] args) {
            new Thread(() -> {
                synchronized (obj) {
                    System.out.println("执行了线程1的代码...");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("结束了线程1的代码...");
                }
            }).start();
        }
    }
    

    如果没有人来唤醒被wait()的线程,那么它就会一直休息,但是wait()也有带参方法,带参的话,即参数就是等待的时间,如果线程超过等待时间还没有人去将它唤醒,线程就会自动离开休息室

    七.notify()和notifyAll()

    对于进入wait Set休息室的被wait()的线程,如果不想让它继续暂停,那么就需要将其进行唤醒,此时就可以使用上述两个方法

    notify()是在Wait Set休息室中随机找一个线程,然后将其唤醒,然后被唤醒的线程重新竞争

    notifyAll()则是将Wait Set里面的所有线程全部唤醒

    wait()和notify()的典型应用有让线程顺序执行,生产者消费者模型等

    八.生产者消费者模型

    一边生产,一边消费,比如卖包子,一遍制作包子(生产者),一遍顾客买包子(消费者),两者的速度不一定匹配,比如生产者需要提前做好一部分包子,有一段时间买包子的速度也大于制作包子的速度;当然也不能无限生产包子,导致产品浪费。所以就需要先制作好一批包子,等消费者买走一部分再进行生产,所以中间需要一个容器存储这些已经生产好的包子

    代码设计:利用wait()和notifyAll()实现

    在第十四个知识整理,会有一个更高级的方式(阻塞队列)对生产者消费者模式进行实现

    容器为一个集合,用于存放已生产的商品,容量为5

    创建一个生产者线程,总数最多生产15个,如果当前生产量超过可以容纳的数量,则进入等待状态,只要生产就可以唤醒消费者

    创建消费者线程,如果集合中的商品不为0,那么就可以进行消费,且消费了就可以唤醒生产者线程

    import java.util.ArrayList;
    import java.util.List;
     
    public class ProducerAndConsumerTest {
        static List<Product> products = new ArrayList<>();
     
        public static void main(String[] args) {
            //生产者
            new Thread(() -> {
                for (int i = 1; i <= 15; i++) {
                    synchronized (products) {
                        //容量不能超过5,否则等待
                        while (products.size() == 5) {
                            try {
                                products.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        Product p = new Product(i);
                        products.add(p);
                        products.notifyAll();
                        System.out.println(Thread.currentThread().getName() + "生产了产品" + p);
                        //添加商品之后,说明有商品了,可以唤醒消费者
                        products.notifyAll();
                    }
     
                }
            }).start();
     
            //消费者1
            new Thread(() -> {
                for (int i = 1; i <= 8; i++) {
                    synchronized (products) {
                        while (products.size() == 0) {
                            try {
                                products.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
     
                        Product p = products.remove(0);
                        System.out.println(Thread.currentThread().getName() + "消费了产品" + p);
                        //消费了产品之后,说明不再是最大容量,所以可以唤醒生产者继续生产
                        products.notifyAll();
                    }
                }
            }).start();
     
            //消费者2
            new Thread(() -> {
                for(int i = 1; i <= 7; i++) {
                    synchronized (products) {
                        while (products.size() == 0) {
                            try {
                                products.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
     
                        Product p = products.remove(0);
                        System.out.println(Thread.currentThread().getName() + "消费了产品" + p);
                        //消费了产品之后,说明不再是最大容量,所以可以唤醒生产者继续生产
                        products.notifyAll();
                    }
                }
            }).start();
        }
    }
     
    class Product {
        private int i;
     
        Product(int i) {
            this.i = i;
        }
     
        @Override
        public String toString() {
            return "Product{" +
                    "i=" + i +
                    '}';
        }
    }
    

    九.什么是线程池?为什么要使用线程池?

    加入有一个服务器,所有用户都可以进来做增删改查操作,加入有1000个用户要进行操作,那我们当然是不能让他们排队等候进行操作(等到头秃了),而是希望他们能够并行执行,所以就要创建1000个线程,每个线程服务一个用户。但是这样真的合理吗?答案显然不是的,线程也是需要占用系统资源的,系统不能无限制的创建线程,这是一种严重的资源浪费!

    举个例子:餐馆有100个顾客,我们要对他们进行服务,但是我们不可能雇佣100个服务员分别对这些顾客服务(估计发不起工资),而是雇佣有限个服务员,当某个服务员有空闲了,再为下一个顾客进行服务,这其实就是线程池的思想。也是享元模式的一个重要体现

    具体我们使用代码演示线程池的例子

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
     
    public class ThreadPoolTest {
        public static void main(String[] args) {
            //创建固定大小为3的线程池
            ExecutorService threadPool = Executors.newFixedThreadPool(3);
            for(int i = 0; i < 6; i++) {
                //submit将任务提交给线程池,然后线程池等待空闲线程执行
                //使用lambda表达式,submit()里面自动实现了run()
                //使用线程池,不用我们去创建线程,start()也不需要我们自己去调用,线程池已经为我们做好了
                //当然,如果想要返回值,也可以将Callable作为参数传入submit()中
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "任务执行");
                });
            }
        }
    }
    

    执行结果
    在这里插入图片描述
    其中有一个核心的ExecutorService的实现类ThreadPoolExecutor,有5个参数

    int corePoolSize 核心线程数目, 也就是最少有几个线程

    int maximumPoolSize 可以容纳的最多的线程数

    long keepAliveTime 针对救急线程最多生存时间

    TimeUnit unit 时间单位 秒

    BlockingQueue workQueue 阻塞队列 如何任务超过了核心线程数,进入队列进行排队,直到有空闲的线程

    其中对于不同的场景我们需要创建不同类型的线程池

    newFIxedThreadPool(); 创建固定大小的线程池。其核心线程数=最大线程数,阻塞队列无界,可以放任意数量的任务。适用于执行多个长时间运行的任务

    newCachedThread; 缓冲线程池。 没有核心线程数,即所有线程都是救急线程,最大线程数的最大值可以为int类型的最大值,可以认为是救急线程可以无线创建,而参数队列是来一个线程就会创建一个新线程。适用于任务书密集但每个任务执行时间较短

    newSingleThreadExecutor(); 创建单线程线程池,创建以后值不能修改。即线程是串行执行的。适用于希望多个任务排队执行的场景

    newScheduleThreadPool(); 带有日程功能安排的线程池,即不是立刻执行的,而是可以设置在未来的某个时间执行(schedule())。也可以让线程按照某个周期执行(scheduleAtFixedRated())

    十.Java内存模型
    在说Java内存模型之前,我们先了解Java的内存结构,即运行时区域,参考https://blog.csdn.net/szy2333/article/details/88723095

    而Java内存模型的主要目标是定义程序中各个变量的访问规则,即JVM中将变量存储到内存和从内存中取出变量这样的细节。

    此处的变量有所不同步,它包含了实例字段,静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程是私有的,不会共享,不存在数据竞争问题。为了获得较高的性能,Java内存模型并没有限制执行引起使用处理器的特定寄存器或者缓冲和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施

    JMM规定了所有变量都储存在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory),线程的工作内存保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方内存中的变量,线程之间值的传递都需要通过主内存来完成
    在这里插入图片描述
    线程1和线程2要进行数据的交换一般要经过两个步骤

    1.线程1把工作内存1中更新过的共享变量刷新到主内存中去

    2.线程2到主内存中读取线程1刷新过的共享变量,然后复制一份到工作内存2中去

    Java内存模型则主要是围绕三个特征建立的,即原子性,可见性,有序性

    原子性:一个操作不能被打断,要么完全执行完毕,要么不执行(和事务原子性类似)

    可见性:一个线程对共享变量做了修改之后,其他线程立即能够看到变量的变化

    a.可以使用volatile关键字实现可见性,volatile的特殊规则保证了变量修改后的新值立刻同步到工作内存中

    b.使用synchronized关键字,在同步方法/同步块时,使用共享变量时会从主内存中刷新变量值到工作内存中,在结束时,会将工作内存中的变量值同步到主内存中去

    c.使用Lock接口,常用ReentrantLock(重入锁)实现可见性。在方法开始位置执行lock.lock(),和synchronized开始位置一样

    d.final关键字:被final修饰的变量,在构造方法一旦初始化完成,并且在构造方法中没有把this引用传递出去,那么其他线程就可以看到final的值(注意:this引用逃匿很危险。其他线程很可能只通过该引用访问到只初始化一般的对象)

    有序性

    对于一个线程内的代码而言,代码执行顺序是依次执行的,但是多线程时,程序的执行就会出现乱序(即指令重排现象和工作内存和主内存同步延迟现象)

    Java提供了两个关键字volatile和synchronized来保证多线程之间的有序性。volatile关键字是本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现

    十一.如何停止线程?

    1正常情况下,run()和call()方法执行完的时候线程会自动结束

    2.设置终止标志位,使线程正常退出,一般会配合while循环使用

    3.使用stop()立即结束线程。但是由于线程无论执行到哪里,是否加锁,stop()都会立即杀死线程,无法保证原子操作能够顺利完成,存在数据错误的风险;同时,有可能对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题

    4.suspend()将线程挂起,停止线程的运行,并未杀死线程。但挂起之后不会释放锁,这样,如果有其他多个线程在等待该锁,程序就会发生死锁现象

    5.interrupt()终止线程。使用这种方法分为两种情况:线程处于阻塞状态,如使用了sleep(),或者使用while( ! isInterruptted()) {…}来判断线程是否中断

    十二.什么是原子操作类?

    在并发问题时,我们可以用两个线程共享一个变量,一个线程对其自增,一个对其自减,由于两个操作是多条指令,而不是一个原子性的动作,所以会造成多线程安全问题。我们之前使用加锁方式可以解决这个问题,但其实,我们也可以通过原子操作类来解决,即自增的指令看做是一个原子,自减也看做是一个原子,就不会发生指令交错

    原子操作类有整型的,布尔的等等,我们利用整型原子做一个示范

    import java.util.concurrent.atomic.AtomicInteger;
     
    public class ThreadConcurrentTest {
        //volatile static int count = 0;   //volatile是不能解决多个线程对一个数据的交错操作问题
        static AtomicInteger count = new AtomicInteger(0);
        public static void main(String[] args) throws InterruptedException {
     
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    //count++;
                    count.getAndIncrement();
                    //System.out.println(Thread.currentThread() + " : " + count);
                }
            });
     
            Thread t2 = new Thread(() -> {
                for(int i = 0; i < 5000; i++) {
                    //count--;
                    count.getAndDecrement();
                    //System.out.println(Thread.currentThread() + " : " + count);
                }
            });
     
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(count);
        }
    }
    

    操作结果输出总是0,说明确实做到了线程安全

    十三.线程安全集合类

    StringBuffer 线程安全

    String 不可变类,都是线程安全的

    Random 随机数类,加锁安全

    Vector 实现了List,并且线程安全

    HashTable 实现了Map,并且线程安全

    5.0之后新增加、的安全集合类

    CopyOnWriteArrayList 实现了List,线程安全且比Vector效率更高(下面会进行浅析)

    ConcurrentHashMap 实现了Map,线程安全并且比HashTable效率更高(下面会进行浅析)

    ConcurrentSkipListMap 实现了Map,线程安全并且可排序,和TreeMap类似,都是对key值进行排序

    BlockingQueue 阻塞队列,实现了Queue,也是线程安全的(下一条进行详解)

    CopyOnWriteArrayList

    之前Vector也是线程安全的,但是由于它对读和写都是加锁的,导致了效率十分低下

    而CopyOnWriteArrayList则是只对写(增删改)操作进行了加锁,而对于读不加锁,因此效率大大提高

    那么它是怎么实现边读边写可以同时进行的呢?在这里,做了一个巧妙的处理,即修改的时候会将原来的旧数组copy一份,成为一个新的数组,如果同时有读和写两个操作,那么会在旧数组中进行读取操作,而在新数组中进行写的工作,写的工作完成后,抛弃旧数组。但是注意,同时进行写的操作是一定会加锁的,比如下图中线程3和线程4就是需要对新数组进行加锁操作

    从实现原理上我们也不难理解,这种方式会在读操作多于写操作会大大提升效率,如果都是写操作,那么效率和Vector差不多

    5.0之后新增加、的安全集合类

    CopyOnWriteArrayList 实现了List,线程安全且比Vector效率更高(下面会进行浅析)

    ConcurrentHashMap 实现了Map,线程安全并且比HashTable效率更高(下面会进行浅析)

    ConcurrentSkipListMap 实现了Map,线程安全并且可排序,和TreeMap类似,都是对key值进行排序

    BlockingQueue 阻塞队列,实现了Queue,也是线程安全的(下一条进行详解)

    CopyOnWriteArrayList

    之前Vector也是线程安全的,但是由于它对读和写都是加锁的,导致了效率十分低下

    而CopyOnWriteArrayList则是只对写(增删改)操作进行了加锁,而对于读不加锁,因此效率大大提高

    那么它是怎么实现边读边写可以同时进行的呢?在这里,做了一个巧妙的处理,即修改的时候会将原来的旧数组copy一份,成为一个新的数组,如果同时有读和写两个操作,那么会在旧数组中进行读取操作,而在新数组中进行写的工作,写的工作完成后,抛弃旧数组。但是注意,同时进行写的操作是一定会加锁的,比如下图中线程3和线程4就是需要对新数组进行加锁操作

    从实现原理上我们也不难理解,这种方式会在读操作多于写操作会大大提升效率,如果都是写操作,那么效率和Vector差不多

    ConcurrentHashMap

    首先,我们要知道HashMap的底层结构,即数组+链表,如下图,每次用hashcode定位到数组下标,下标相同则可以看成一个桶,每次put()和get()都需要先用hashcode先定位出桶的位置,再在桶中进行操作

    HashTable也是基于Map实现的一个线程安全类,但是由于它是锁住了整个集合,造成了性能比较低,所以提出的所谓更高效率ConcurrentHashMap又是怎么样去实现的?

    其实,ConcurrentHashMap相对于HashTable只是缩小了加锁的范围,HashTable是在整个集合中加了一把大锁,只要put()或者get(),都会将集合锁住,也就是所有操作都是串行执行的,所以效率低下。而ConcurrentHashMap则是给不同的桶进行加锁,比如线程1定位到了桶1,线程2定位到了桶3,那么这两个桶是各自执行桶内的操作,互不影响,即可以并发执行。只有在多个线程同时定位到一个桶中进行操作,才会对该桶进行加锁,所以,效率提高了很多!

    在这里插入图片描述

    十四.阻塞队列

    首先了解队列,队列(Queue)是一种数据结构,遵循着先进先出的规则

    阻塞队列(BlockingQueue),顾名思义,是队列的一种,但是在队列的基础上又支持了两个附加操作的队列,它们分别是:

    支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满

    支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空

    那么根据这种特性,我们就可以联想到一种模式:生产者消费者模式

    为什么呢?在之前我们对于生产者消费者模式使用wait()和notifyAll()进行了一次实现。我们知道生产者等到生产达到饱和时,就会进入等待状态,直至有消费者进行了消费进行唤醒;同样的,消费者如果将生产的商品消费为空时,就会进入等待状态,直至有生产者生产了才会被唤醒。这个方式使用阻塞队列刚好,因为put()方法是达到阻塞队列要求的容量之后就会阻塞元素的插入,只会队列不为满,而take()则是当队列为空时阻塞,直至不为空

    接下来我们对于代码进行一下具体实现

    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
     
    /*
        使用阻塞队列实现生产者消费者模型
    * */
    public class ProducerAndConsumerTest2 {
        static BlockingQueue<Product> queue = new LinkedBlockingQueue<>(5);
        public static void main(String[] args) {
            new Thread(() -> {
                for(int i = 1; i <= 15; i++) {
                    Product p = new Product(i);
                    try {
                        queue.put(p);
                        System.out.println(Thread.currentThread().getName() + "生产了商品" + p);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
     
            new Thread(() -> {
                for(int i = 1; i <= 7; i++) {
                    try {
                        Product p = queue.take();
                        System.out.println(Thread.currentThread().getName() + "消费了商品" + p);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
     
            new Thread(() -> {
                for(int i = 1; i <= 8; i++) {
                    try {
                        Product p = queue.take();
                        System.out.println(Thread.currentThread().getName() + "消费了商品" + p);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    

    执行结果
    在这里插入图片描述

    十五.悲观锁和乐观锁

    什么是悲观锁?

    悲观锁就是总是假设最坏的情况,总觉得每次拿数据的时候会被别人修改,所以不管什么情况先上个锁再说,这样的话,只要别人想修改数据就只有等到他拿到锁才可以。之前学过的synchronized就是悲观锁,对于synchronized不再讨论

    什么是乐观锁?

    与悲观锁相反,它总是把别人都想的很好,每次去拿数据的时候别人不会修改自己的数据,所以不会上锁。而是拿数据之前先判断别人有没有对这个数据进行修改,如果修改了,那就会重新获取最新值,然后继续判断,直至确定当前值没有被修改再去操作。CAS体现的就是乐观锁

    什么是CAS机制?

    CAS就是compare and swap(没有使用synchronized),它不会给共享资源加锁,而是做一个尝试:先拿到旧值,查看旧值是否和共享区域的值相等,如果不等,那么说明别的线程改动了共享区域的值,我的操作失败;如果相等,那么就让我的操作成功。那么操作失败了怎么办?没关系,重新尝试就可以。之前用过的原子变量类里面的getAndSetInt()就使用的是这种CAS机制

    获取源码并进行浅析

    在这里插入图片描述

    int var5;
    //操作失败,没有关系,重新进行尝试
    do {
        //获得共享区域内的最新值
        var5 = this.getIntVolatile(var1, var2);
        //比较并交换,如果比较后值相等,取反为false,跳出循环,并获得最新值var5,否则继续尝试
    } while(!this.compareAndSwapInt(var1, var2, var5, var4));
    return var5;
    

    两种锁的使用场景

    对于两种锁的介绍,我们可以看到两种各自有各自的好处。乐观锁适用于多读场景(即冲突真的很少发生的时候),这样就可以大大减少锁的开销,加大了系统的整个吞吐量。但是如果是多写的情况,一般就会经常产生冲突,这就会导致上层应用不断进行重新尝试,反而降低了性能,所以在多写的情况下,悲观锁就更适合使用一些

    十六.CountDownLatch

    CountDownLatch是一个同步工具类,它允许一个或者多个线程一直等待,直到其他线程执行完再执行

    原理是通过一个计数器实现的,计数器的初始化值为线程的数量,每当一个线程完成来了自己的任务之后,计数器的值就相应减一。当计数器到达0时,表示所有线程都已完成任务,然后在闭锁上等待的线程就可以恢复执行任务

    构造方法需要传一个count(int类型)参数,是用等待线程数量来进行初始化

    计数器count是闭锁需要等待的线程数量,只能被设置一次,且CountDownLatch没有提供任何机制去重新设置计数器count

    与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在其他线程启动后立即调用CountDownLatch.wait(),这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务

    其他线程必须引用CountDownLatch闭锁对象,因为它们需要通知CountDownLatch对象。这种通知机制是通过CountDownLatch.countDown()来完成的,每调用一次,count的值减一,因此其它线程都调用这个方法,直到count为0,主线程就可以通过await()恢复自己的任务

    实际应用:比如10个玩家进行王者荣耀时,为了保证游戏的公平性,就需要等待10个玩家都加载好以后才能进入游戏,那么可以用主线程来进行控制等待,十个玩家(十个线程)都进行了countDown()之后,主线程恢复运行,即可以开始游戏

    代码实现

    import java.util.Arrays;
    import java.util.Random;
    import java.util.concurrent.CountDownLatch;
     
    public class CountDownLatchTest {
        public static void main(String[] args) throws InterruptedException {
            //十个玩家,所以是十个计数
            CountDownLatch latch = new CountDownLatch(10);
            System.out.println("玩家准备");
            String[] str = new String[10];
            Random random = new Random();
            for(int i = 0; i <10; i++) {
                //因为匿名内部类只能调用外部被final修饰的常量,所以将i赋值给x
                final int x = i;
                //共创建十个线程
                new Thread(() -> {
                    for(int j = 0; j <= 100; j++) {
                        try {
                            //睡眠时间使用随机数,实现游戏加载的不确定性
                            Thread.sleep(random.nextInt(100));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //锁住数组对象
                        synchronized (str) {
                            str[x] = (j + "%");
                            System.out.print("\r" + Arrays.toString(str));   //使用\r,跳到表头
                        }
                    }
                    //计数减一
                    latch.countDown();   
                }).start();
            }
     
            latch.await();
            System.out.println("\n进入游戏");
        }
    }
    

    测试结果
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 学习多线程一.认识线程1.1 概念1.2 创建线程二.Thread 类及常见方法2.1 Thread 的常见构造方法2.2 Thread 的几个常见属性2.3 启动一个线程2.4 中断一个线程2.5等待一个线程-join()2.6 获取当前线程引用2.7 休眠当前...

    一.认识线程

    1.1 概念

    我们设想如下场景: 一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会
    忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分
    别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一
    家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队
    执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
    那这个和多进程又有什么区别的,最大的区别就是这些执行流之间是否有资源的共享。比如之前的多进程例子中,每
    个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面
    我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

    进程是系统分配资源的最小单位,
    线程是系统调度的最小单位。
    一个进程内的线程之间是可以共享资源的。
    每个进程至少有一个线程存在,即主线程(此处主线程为C语言的入口函数级别的线程)。

    • 我们以前都是写完代码然后直接运行得到结果,那么当你run或者debug时,是启动了什么程序?你知道真实的java进程的运行过程吗?
      |
      |
      .首先运行main方法(本质是调用java包名.类名 来启动java程序——》启动之后就相当于java进程运行起来了)

      那么接下来进程直接执行代码行输出结果吗??

      当然不是!!

      比如此时java程序是在windows上运行,这个java.exe是用C语言编写的,此时java命令代码执行是启动系统main方法,一般是C语言的main入口函数(系统级别的main线程,也就是系统级别的主线程),

    执行过程:

    在这里插入图片描述

    1.2 创建线程

    方法1: 继承 Thread 类

    可以通过继承 Thread 来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过
    Thread.currentThread() 来获取当前线程的引用。

    class MyThread extends Thread {
    @Override
    public void run() {
    System.out.println("这里是线程运行的代码");
      }
    } 
    
    MyThread t = new MyThread();
    t.start(); // 线程开始运行 

    方法二:实现 Runnable 接口
    通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。
    该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用

    class MyRunnable implements Runnable {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName() + "这里是线程运行的代码");
      }
    } 
    Thread t = new Thread(new MyRunnable());
    t.start(); // 线程开始运行 

    方法三:其他变形 方法

    // 使用匿名类创建 Thread 子类对象
    Thread t1 = new Thread() {
    @Override
    public void run() {
    System.out.println("使用匿名类创建 Thread 子类对象");
      }
    };
    // 使用匿名类创建 Runnable 子类对象
    Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("使用匿名类创建 Runnable 子类对象");
      }
    });
    // 使用 lambda 表达式创建 Runnable 子类对象
      Thread t3 = new Thread(() ->  System.out.println("使用匿名类创建 Thread 子类对象"));
      Thread t4 = new Thread(() -> {
      System.out.println("使用匿名类创建 Thread 子类对象");
    }); 
    
    

    二.Thread 类及常见方法

    Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
    用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述
    一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理

    2.1 Thread 的常见构造方法

    方法 说明
    Thread() 创建线程对象
    Thread(Runnable target) 使用 Runnable 对象创建线程对象
    Thread(String name) 创建线程对象,并命名
    Thread(Runnable target, String name 使用 Runnable 对象创建线程对象,并命名
    【了解】Thread(ThreadGroup group,Runnable target 线程可以被用来分组管理,分好的组即使线程组,这个目前我们了解即可
    Thread t1 = new Thread();
    Thread t2 = new Thread(new MyRunnable());
    Thread t3 = new Thread("这是我的名字");
    Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

    2.2 Thread 的几个常见属性

    属性 获取方法
    ID getId
    名称 getName
    状态 getState
    优先级 getPriority
    是否后台线程 isDaemon
    是否存活 isAlive
    是否被中断 isInterrupted
    • ID 是线程的唯一标识,不同线程不会重复
    • 名称是各种调试工具用到
    • 状态表示线程当前所处的一个情况,下面我们会进一步说明
    • 优先级高的线程理论上来说更容易被调度到
    • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
    • 是否存活,即简单的理解,为 run 方法是否运行结束了
    • 线程的中断问题,下面我们进一步说明
    public class ThreadDemo {
    public static void main(String[] args) {
    Thread thread = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
    try {
      System.out.println(Thread.currentThread().getName()   + ": 我还活着");
    Thread.sleep(1 * 1000);
       } catch (InterruptedException e) {
    e.printStackTrace();
      }
     } 
      System.out.println(Thread.currentThread().getName()    + ": 我即将死去");
     });
      System.out.println(Thread.currentThread().getName()
      + ": ID: " + thread.getId());
      System.out.println(Thread.currentThread().getName()
      + ": 名称: " + thread.getName());
      System.out.println(Thread.currentThread().getName()
      + ": 状态: " + thread.getState());
      System.out.println(Thread.currentThread().getName()
      + ": 优先级: " + thread.getPriority());
      System.out.println(Thread.currentThread().getName()
      + ": 后台线程: " + thread.isDaemon());
      System.out.println(Thread.currentThread().getName()
      + ": 活着: " + thread.isAlive());
      System.out.println(Thread.currentThread().getName()
      + ": 被中断: " + thread.isInterrupted()); 
      thread.start();
      while (thread.isAlive()) {
      System.out.println(Thread.currentThread().getName()
      + ": 状态: " + thread.getState());
        }
       }
    } 
    
    

    2.3 启动一个线程

    之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行
    了。

    • 覆写 run 方法是提供给线程要做的事情的指令清单

    • 线程对象可以认为是把 李四、王五叫过来了

    • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了
      面试题: 一定要注意 run 方法和 start 方法是不同的,启动线程必须要调用 start 方法。

      !!此处要认识到start是启动某个线程,run是执行线程内部的代码(即线程要做的事情),线程没有启动(start),run方法不会执行到,因为线程只有start才能进入就绪态,等待系统调度,调度到时线程进入运行态,才能执行run方法。

    2.4 中断一个线程

    例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉
    及到我们的停止线程的方式了。

    目前常见的有以下两种方式:
    1. 通过共享的标记来进行沟通
    2. 调用 interrupt() 方法来通知

    实例1:

    此处使用共享标记来实现中断线程(也就是设置布尔变量)

    public class ThreadDemo {
    private static class MyRunnable implements Runnable {
    public volatile boolean isQuit = false;
     @Override
     public void run() {
     while (!isQuit) {
      System.out.println(Thread.currentThread().getName()
      + ": 别管我,我忙着转账呢!");
     try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      e.printStackTrace();
       }
      } 
     System.out.println(Thread.currentThread().getName()
     + ": 啊!险些误了大事");
      }
     } 
    public static void main(String[] args) throws    InterruptedException {
      MyRunnable target = new MyRunnable();
      Thread thread = new Thread(target, "李四");
      System.out.println(Thread.currentThread().getName()
      + ": 让李四开始转账。");
      thread.start();
      Thread.sleep(10 * 1000);
      System.out.println(Thread.currentThread().getName()
      + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
      target.isQuit = true;
      }
    } 

    这种方法虽然可以实现中断线程,但是当线程调用了sleep方法,并且休眠时间较长,就无法及时中断线程了。所以不太提倡。

    实例2:

    public class Thread2 {
    private static class MyRunnable implements Runnable {
    @Override
    public void run() {
    // 两种方法均可以
    while (!Thread.interrupted()) {
    //while (!Thread.currentThread().isInterrupted()) {
      System.out.println(Thread.currentThread().getName()
      + ": 别管我,我忙着转账呢!");
    try {
       Thread.sleep(1000);
      } catch (InterruptedException e) {
       e.printStackTrace();
      System.out.println(Thread.currentThread().getName()
     + ": 有内鬼,终止交易!");
    break;
        }
      }
       System.out.println(Thread.currentThread().getName()
      + ": 啊!险些误了大事");
       }
     }
     public static void main(String[] args) throws      InterruptedException {
      MyRunnable target = new MyRunnable();
      Thread thread = new Thread(target, "李四");
      System.out.println(Thread.currentThread().getName()
      + ": 让李四开始转账。");
      thread.start();
      Thread.sleep(10 * 1000);
      System.out.println(Thread.currentThread().getName()
      + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
       thread.interrupt();
      }
    } 

    重点说明下第二种方法:

    1. 通过 thread 对象调用 interrupt() 方法通知该线程停止运行

    2. thread 收到通知的方式有两种:
      2.1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除
      中断标志
      2.2. 否则,只是内部的一个中断标志被设置,thread 可以通过

      2.2.1. Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
      2.2.2. Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

    第二种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到

    2.5等待一个线程-join()

    有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决
    定是否存钱,这时我们需要一个方法明确等待线程的结束。

    public class Thread2 {
    public static void main(String[] args) throws InterruptedException {
    Runnable target = () -> {
    for (int i = 0; i < 10; i++) {
    try {
         System.out.println(Thread.currentThread().getName()
        + ": 我还在工作!");
       Thread.sleep(1000);
     } catch (InterruptedException e) {
          e.printStackTrace();
         }
        }
     System.out.println(Thread.currentThread().getName() + ": 我结束了!");
       };
    Thread thread1 = new Thread(target, "李四");
    Thread thread2 = new Thread(target, "王五");
    System.out.println("先让李四开始工作");
    thread1.start();
    thread1.join();
    System.out.println("李四工作结束了,让王五开始工作");
    thread2.start();
    thread2.join();
    System.out.println("王五工作结束了");
      }
    } 
    
    

    附:

    方法 说明
    public void join() 等待线程结束
    public void join(long millis) 等待线程结束,最多等 millis 毫秒
    public void join(long millis, int nanos) 同理,但可以更高精度

    2.6 获取当前线程引用

    这个方法之前已经使用过了,相信大家一定有了一定的认识了吧

    方法 说明
    public static Thread currentThread(); 返回当前线程对象的引用
    public class ThreadDemo {
    public static void main(String[] args) {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName());
      }
    } 

    2.7 休眠当前线程

    有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证休眠时间是大于等于休眠时间的

    方法 说明
    public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒
    public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
    public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
    System.out.println(System.currentTimeMillis());
    Thread.sleep(3 * 1000);
    System.out.println(System.currentTimeMillis());
      }
    } 

    3. 线程的状态

    3.1观察线程的所有状态

    线程的状态是一个枚举类型 Thread.State

    public class ThreadState {
    public static void main(String[] args) {
    for (Thread.State state : Thread.State.values()) {
    System.out.println(state);
      }
     } 
    } 
    N
    EW
    RUNNABLE
    BLOCKED
    WAITING
    TIMED_WAITING
    TERMINATED

    3.2线程状态和状态转移的意义

    在这里插入图片描述

    大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。

    在这里插入图片描述

    刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态

    当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接
    待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
    当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入 BLOCKED 、 WATING 、 TIMED_WAITING 状态
    如果李四、王五已经忙完,为 TERMINATED 状态。
    所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。

    在这里插入图片描述

    后序继续更新奥!!!

    展开全文
  • 一. 前言 NetDiscovery 是本人开发的一款基于 Vert.x、RxJava 2 等框架实现的通用...本文列举一些爬虫框架常见多线程使用场景。 2.1 爬虫的暂停、恢复 暂停和恢复是最常见的爬虫使用场景,这里借助 CountDownLa...
  • 多线程使用的主要目的在于举个简单的例子伪代码多线程常见应用场景 多线程使用的主要目的在于 1、吞吐量:你做WEB,容器帮你做了多线程,但是他只能帮你做请求层面的。简单的说,可能就是一个请求一个线程。或多...
  • 常见锁的使用场景

    千次阅读 2019-08-11 00:12:08
    Semaphore(信号量)-允许线程同时访问(来自javaguide) synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定线程同时访问某个资源。示例代码如下: /** * * @...
  • 08_1_多线程使用

    2019-11-06 19:39:01
    多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法...
  • Qt中多线程使用方法

    2021-02-28 17:45:56
    Qt中有多种创建线程的方式,每一种的应用场景使用方式都有些区别, 这里主要简单介绍Qt里面的几大创建线程的方法,以及使用注意事项。 QThread 使用QThread创建线程是我们最常见的一种方式,步骤如下: 继承...
  • 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。 并行与并发: 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu...
  • 4、说说几种常见的线程池及使用场景。 5、线程池都有哪几种工作队列? 6、怎么理解无界队列和有界队列? 7、线程池中的几种重要的参数及流程说明。 8、什么是反射机制? 9、说说反射机制的作用。 10、反射机制会不会...
  • 2.Java多线程 2.1.线程池有哪些常用线程池? 有什么区别? 在什么场景下进行使用 https://www.cnblogs.com/sachen/p/7401959.html 2.2 synchronized底层实现原理及锁优化 ...2.3 线程同步和通信的方式 ...
  • 几种常见的线程池及使用场景

    千次阅读 2020-03-08 19:54:58
    为什么要使用线程池? 创建线程和销毁线程的花销是比较大的,...1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的。 2、方便管理 可以编写线...
  • Java完成多线程间的等待功能:场景1:一个线程等待其他多个线程都完成后,再进行下一步操作(如裁判员计分功能,需要等待所有运动员都跑完后,才去统计分数。裁判员和每个运动员都是一个线程)。场景2:多个线程都...
  • 博主其他相关文章:《Java高级工程师常见面试题-总结》 1. Java创建线程之后,...2. 常用的线程池模式以及不同线程池的使用场景 newCachedThreadPool: 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maxi...
  • 前言:  前面一篇介绍了并发编程的一些是名词解释,并罗列了相关疑惑点。...下面介绍几种常见多线程创建-使用方式 1、编写自己的任务类,继承Thread类,复写Thread类的run方法,在驱动线程中创建任务...
  • 高并发编程系列:4种常用Java线程锁的特点,性能比较、使用场景 ...   在Java并发编程中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑...Java提供了多种多线程锁机制的实现方式,常见的有: ...
  • 在UI界面程序中,使用到多进程与多线程是很常见场景,有时候我们需要将一些耗时的操作放在其他的线程或者进程中,避免卡死主线程。而且利用多线程加Qt的信号槽机制我们可以在子进程中实现事件监听,实时监测进程间...
  • Java多线程总结

    2021-05-09 22:45:12
    多线程并行与并发上下文切换进程与线程多线程的好处多线程的应用场景Thread类的常见方法多线程的基本使用线程的状态 state线程的创建 new线程的启动 start()线程的休眠 sleep()线程的等待 join()线程的中断 ...
  • JAVA 多线程 线程池

    2021-03-19 14:39:44
    多线程常见应用场景: 1、后台任务,例如:定时向大量(100w以上)的用户发送邮件; 2、异步处理,例如:发微博、记录日志等; 3、分布式计算 A>Executors类和Executor类的关系 Executor是含有执行提交...
  • 通过表的归纳和总结说明: ...这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的 2)需要进行大量计算的优先使用线程(CPU频繁切换) 所
  • 初识多线程1.1 概念1.2 多线程使用场景1.3 jconsole1.4 线程的状态1.4.1 线程所有状态1.4.2 状态转移图2. 线程控制2.1 创建线程2.1.1 使用Thread类重写run方法创建2.1.2 实现Runnable接口2.1.3 其他变形2.1.4 解读...
  • java多线程之线程封闭

    2019-04-04 19:26:45
    常见的应用场景: 在JDBC中的Connection对象并不是线程安全的。在典型的Servlet应用中,请求是同步的,请求直接使用用Connection对象访问数据库,使用完再返回给链接池给下一个请求使用,并且在链接返回之前不会给...
  • Redis是一个key-value存储系统,现在在各种系统中的使用越来越,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景。Redis特性一个产品的使用场景肯定是需要根据产品的特性,...
  • 为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。 一面50min 自我介绍 Java集合 LinkedList与ArrayList fail-fast CAS reentrantlock ...
  • 前言:  在新增的Concurrent包中,BlockingQueue很好的...本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景。 认识BlockingQueue 阻塞队列,顾名思义,首先它是一个队列,而一个
  • 文章目录1. synchronize 和 lock的区别2.... nsq和kafka的使用场景,不同点,以及不同的场景怎么选择 1. synchronize 和 lock的区别 2. 多线程的状态流转、等待和阻塞的区别 3. zookeeper 角色 4. 分布式锁的原理 5.
  • 在新增的Concurrent包中,...本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景。 题目: 面试题:有两个线程A,B, A线程每200ms就生成一个[0,100]之间的随机数, B线程每...
  • 《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...
  • 2.多线程使用场景多线程,故名思议,就是一段程序上有多个线程在执行,由于CPU的快速切换,是的多个线程看似是同时执行的。常见多线程应用场景如下所示:1、servlet多线程。2、数据库用到的多线程。3、tomcat,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 877
精华内容 350
关键字:

多线程常见使用场景