栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java多线程 -- Thread

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java多线程 -- Thread


一、多线程技术概述

1、线程与进程

进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

2、线程调度

分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的为抢占式调度。

CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,
只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时
刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使
用率更高。

3、同步与异步

同步:排队执行 , 效率低但是安全.(在一条道路上一起执行)

异步:同时执行 , 效率高但是数据不安全.(在多条道路上同时执行)

4、并发与并行

并发:指两个或多个事件在同一个时间段内发生。

重点为“一个时间段区间内”,如Cpu在该时间段内同时执行了多件事情

并行:指两个或多个事件在同一时刻发生(同时发生)。

重点为“某一时刻”,如CPU为多核,可以在某一时刻执行不同的事情。

二、线程常用方法

1、Thread类常用方法

具体可以参照jdk的API,本次仅列出部分常用方法:

方法概述
1.start()启动当前线程;调用当前线程的run()方法
2.run()通常重写继承Thread类中的此方法,将创建多线程要执行的操作声明在此方法中
3.currentThread()静态方法,返回执行当前代码的线程
4.getName()获取当前线程的名字
5.setName()设置当前线程的名字
6.setDaemo(boolean on)将此线程标记为daemon线程或用户线程
7.join()线程a中调用线程b的join(),此时线程a就会进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
8.sleep(long millis)让当前线程“休眠”指定millis毫秒。在改时间段内,当前线程处于阻塞状态
9.isAlive()判断当前线程是否存活

2、线程优先级
变量和类型字段描述
static intMAX_PRIORITY线程拥有最大优先级
static intMIN_PRIORITY线程拥有最低优先级
static intNORM_PRIORITY分配给线程的默认优先级

三、线程的创建各种方法

1、方式一:继承Thread类
  • 步骤
  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()方法 –>将此线程执行的操作声明在run()中
  3. 创建Thread类的子类对象
  4. 通过此对象调用start()方法

2、方式二:实现Runnable接口
  • 步骤
  1. 创建一个实现Runnable接口的类
  2. 实现类中去实现Runnable接口中的抽象方法:run()
  3. 创建实现类对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()

3、方式三:实现Callable接口(不常用),带返回值的接口
  • 步骤
  1. 创建一个实现Callable接口的类
  2. 实现call()方法,将线程中需要执行的操作声明在call()中,可以有返回值
  3. 创建实现类对象
  4. 将此对象作为参数传递到FutureTask的构造器中,比高创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,并创建Thread对象,再调用start().
  6. 获取Callable中call()方法的返回值

4、比较创建线程的方式

  • 实际开发中:优先选择实现接口方法,常用Runnable接口
  • 优点:
  1. 实现的方式没有类单继承的局限性
  2. 实现的方式更适合来处理多个线程共享数据的情况(可以把实现Runnable接口的实现类看做一个任务,多个线程共同执行一项任务)

四、线程的生命周期

1、新建和就绪状态

      当程序使用new关键字创建一个线程以后,该线程就处于新建状态,此时它和其他的JAVA对象一样,仅仅由JAVA虚拟机为其分配内存,并初始化成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

      当线程对象调用start()方法以后,该线程进入就绪状态,JAVA虚拟机会为其创建方法调用栈和程序计数器。处于这种状态中的线程并没有开始运行,只是表示当前线程可以运行了,至于什么时候运行,则取决于JVM里线程调度器的调度。所以线程的执行是由底层平台控制, 具有一定的随机性。

2、运行和阻塞状态

       当处于就绪状态的线程获得CPU,它就会执行run()方法(所以run()方法是由线程获得CPU以后自动执行),线程就进入了运行状态。如果一个计算机只有一个CPU,那么在任意时刻只有一个线程处于运行状态。相对的,如果有多个CPU,那么在同一时刻就可以有多个线程并行执行。但是,当处于就绪状态的线程数大于处理器数时,仍然会存在多个线程在同一CPU上轮换执行的现象,只是计算机的运行速度非常快,人感觉不到而已。

      当一个线程开始运行后,它不可能一直持有CPU(除非该线程执行体非常短,瞬间就执行结束了)。所以,线程在执行过程中需要被中断,目的是让其它线程获得执行的CPU的机会。线程的调度细节取决于底层平台所采用的策略。对于抢占式策略的系统而言,系统会给每一个可执行线程一个时间段来处理任务,当该时间结束后,系统就会剥夺该线程所占用资源(即让出CPU),让其它线程获得执行机会。

       所有的现代桌面和服务器操作系统都采用抢占式调度策略,但一些小型设备,如:手机,则可能采用协作式调度策略。在这样的系统中,只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源。也就是说,此时必须该线程主动放弃占用资源,才能轮到其他就绪状态的线程获得CPU,不然必须要等当前线阻塞/死亡以后,其他线程才有机会运行。

       当如下情况发生时,线程会进入阻塞状态:

  1.  线程调用sleep()方法主动放弃占用的处理器资源;
  2.  线程调用了一个阻塞式IO方法,在该方法返回以前,该线程被阻塞;
  3.   线程试图获得一个同步监视器,但该监视器被其他线程持有;
  4.  线程在等待某个通知;
  5.  线程调用suspend()方法将该线程挂起。但这个方法容易导致死锁,不建议使用;

       当正在执行的线程被阻塞以后,其他线程可以获得执行的机会,被阻塞的线程会在合适的时候进入就绪状态,而不是进入运行状态。也就是说,当线程阻塞解除后,必须重新等待线程调度器再次调度它,而不是马上获得CPU。所以针对上述线程阻塞情况,如何让线程重新进入就绪状态,有如下几种情况:

  1.  调用sleep()方法的线程经过了指定时间;
  2. 线程调用的阻塞式IO方法已经返回;
  3. 线程成功地获得了试图取得的同步监视器;
  4.  线程在等待通知时,其他线程发出了一个通知;
  5.   处于挂起状态的线程被调用了resume()恢复方法;

线程从阻塞状态只能进入就绪状态,而不能直接进入运行状态。而就绪状态到运行状态之间的转换通常不受程序控制,而由系统线程调度所决定,当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器器资源时,该线程进入就绪状态。但有一个方法可以控制线程从运行状态转为就绪状态,那就是yiled()方法。

3、线程死亡

线程会在如下几种情况结束(结束后就处于死亡状态):

  1. run()/call()方法执行完成,线程正常结束;
  2. 线程抛出一个未捕获的Exception或Error;
  3. 直接调用线程的stop()方法结束该线程——该方法已过时且容易导致死锁,不建议使用。

五、线程同步
  • 关键字:synchronized:任何对象都能作为锁放在synchronized(){}的括号里,通常this表示当前类实现对象充当锁

1、同步代码块:
synchronized(this){

​ //要执行的操作

}

2、同步方法:

哪个类调用这个同步方法,就用这个类的this充当锁

public synchronized boolean sale(){

​ //要执行的操作

}

(注意同步方法声明为静态时,使用调用该方法的类名.class当做锁)

3、ReentrantLock(显示锁)

两个构造方法:

构造器描述
ReentrantLock()创建一个 ReentrantLock的实例。
ReentrantLock(boolean fair)使用给定的公平策略创建 ReentrantLock的实例。

常用方法:lock() //获得锁 ; unlock() //尝试释放此锁定

六、线程通信

1、线程通信的例子

使用以下厨师做菜,服务员端菜的例子。线程交替执行

public class Demo {

    

    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    //厨师
    static class Cook extends Thread {
        private Food food;

        public Cook(Food food) {
            this.food = food;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) {
                    food.setNameAndTaste("老干妈小米粥", "香辣味");
                } else {
                    food.setNameAndTaste("煎饼果子", "甜辣味");
                }
            }
        }
    }

    //服务员
    static class Waiter extends Thread {
        private Food food;

        public Waiter(Food food) {
            this.food = food;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                food.get();
            }
        }
    }

    //食物
    static class Food {
        private String name;
        private String taste;
        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndTaste(String name, String taste) {
            if (flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        public synchronized void get() {
            if (!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }


    }

}

2、涉及到的三个方法
  1. wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器。
  2. notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就会唤醒优先级最高的线程,如优先级一致则先唤醒最先进入阻塞状态的线程。
  3. notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

七、线程池
  • 背景:在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

1、线程池概述

线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

Java中已经提供了创建线程池的一个类:Executor

而我们创建时,一般使用它的子类:ThreadPoolExecutor。

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

2、线程池的好处
  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性。

3、Java中的常见四种线程池

1、CachedThreadPool:可缓存的线程池

该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

public class Demo {
    
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        //向线程池中 加入 新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

2、FixedThreadPool:定长的线程池

有核心线程,核心线程的即为最大的线程数量,没有非核心线程

public class Demo {
    
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

3、SingleThreadPool:单线程线程池

只有一条线程来执行任务,适用于有顺序的任务的应用场景。

public class Demo {
    
    public static void main(String[] args) {
        //效果与定长线程池 创建时传入数值1 效果一致.

        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

4、SecudleThreadPool:周期性任务定长线程池

周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

public class Demo {
    
    public static void main(String[] args) {

        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("俩人相视一笑~ 嘿嘿嘿");
            }
        }, 5, TimeUnit.SECONDS);

        
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("俩人相视一笑~ 嘿嘿嘿");
            }
        }, 5, 2, TimeUnit.SECONDS);
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/592108.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号