zoukankan      html  css  js  c++  java
  • Java多线程系列之:多线程一些基本概念

    一,基础概念

    1,CPU核心数和线程的关系

      CPU核心数:最早的cpu是单核的。后来出现了多核cpu(2核,4核)

      CPU和线程的个数是1:1的关系。比如4核可以允许4个线程同时运行。后来intel提出了超线程的概念。使cpu和线程个数1:2。

    2,CPU时间片轮转机制

      给每一个进程分配一个时间段,这个时间段就被称为进程的时间片 ---> 这个进程允许运行的时间。
      不同进程在cpu上执行,cpu需要进行不同进程之间的切换。每次切换需要耗费5000-20000个时钟周期。这其实是浪费了cpu的资源。
      我们在开发时,尽量减少让cpu去进行进程间的切换。

    3,什么是线程和进程

      进程:程序运行进行资源分配的最小单位。一个进程内部有多个线程,多个会共享这个进程的资源
      线程:CPU调度的最小单位,线程不拥有资源。线程依附于进程。

    4,并行和并发

      并行:某一个时间点,可以处理的事情(同一时刻,可以处理事情的能力)
      并发:与时间单位相关,某个时间段内,可以处理的事情(单位时间内可以处理事情的能力)

    二,认识Java里的线程

    1,Java里的程序天生就是多线程的,比如我们执行main方法时,并不是只有main这个主线程,还有别的线程在运行:

    /**
     * java天生就是多线程的
     */
    public class OnlyMain {
        public static void main(String[] args) {
            
            //Java虚拟机线程管理的接口。
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            
            //通过这个类可以拿到当前应用程序有多少个线程
            ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
            
            for (ThreadInfo threadInfo:threadInfos){
                System.out.println("["+threadInfo.getThreadId()+"] "+threadInfo.getThreadName());
            }
            /**
             * 打印结果:说明执行main方法时至少启动了5个线程
             [5] Monitor Ctrl-Break
             [4] Signal Dispatcher
             [3] Finalizer
             [2] Reference Handler
             [1] main                      main方法线程
             */
        }
    }

    2,启动新线程的三种方式:

    /**
     * 创建线程的三种方式
     */
    public class NewThread {
    
        /**
            方式一:扩展自Thread类
        */
        private static class UseThread extends Thread{
            @Override
            public void run() {
                System.out.println("i am extends Thread");
            }
        }
    
        /**
            方式二:实现Runnable
         */
        private static class UseRun implements Runnable{
            @Override
            public void run() {
                System.out.println("i am implements Runnable");
            }
        }
    
        /*
            方式三:实现Callable接口,允许有返回值
         */
    
        private static class UseCall implements Callable<String>{
            @Override
            public String call() throws Exception {
                System.out.println("i am implements Callable");
                return "CallResult";
            }
        }
    
        public static void main(String[] args) throws InterruptedException,ExecutionException{
    
            //Thread启动线程
            UseThread useThread = new UseThread();
            useThread.start();
    
            //Runnable启动线程
            UseRun useRun = new UseRun();
            new Thread(useRun).start();
    
            //Callable启动线程
            UseCall useCall = new UseCall();
            /**
             * 注意:
             * 1,Callable是不能直接交给Thread的
             * 2,可以把Callable包装成Runnable。FutureTask实现了Runnable接口
             * 3,包装成Runnable后,交给Thread
             */
            FutureTask<String> futureTask = new FutureTask<String>(useCall);
            new Thread(futureTask).start();
            //我们可以从Callable拿到返回值。注意:get()方法是阻塞的
            String result = futureTask.get();
            System.out.println(result);
        }
    }

    3,Java提供了Thread类,为什么还要提供Runnable接口?

    从面向对象的角度思考:Java是单继承的,提供Runnable接口可以多实现。

    4,如何让Java里的线程安全的停止工作?

    4.1,三种方式:

      方式一:线程正常运行结束

      方式二:运行过程中抛出了异常

      方式三:Java提供的方法:

          suspend():调用该方法后,线程是不会释放资源的。比如该线程有锁,他不会释放这把锁。容易发生死锁
          stop():调用该方法后,强行终止线程。无法保证线程资源正常释放。
          resume():

      方式四:建议使用的方法(中断线程安全的方法):

           interrupt():中断一个线程,并不是强行关闭这个线程。只是把一个中断标志设置为true
          isInterrupted():判定当前线程是否处于中断状态。判断中断标志是否为true
          静态的interrupted():判定当前线程是否处于中断状态。 把中断标志改为false

    4.2,注意:

    java线程是协作式进行工作的,所以别的线程调用interrupt()并不是强行关闭这个线程,而是对该线程打个招呼。该线程会不会立即停止工作,完全由该线程自己做主。

    示例代码:

    public static void main(String[] args){
           UseRun useRun = new UseRun();
           Thread thread = new Thread(useRun);
           //main线程对thread线程打了个招呼,至于thread线程会不会停止,完全自己做主
           thread.interrupt();
    }

    三,深入理解Java线程

    1,线程的生命周期

    2,start()和run()的区别

      thread.start():开启一个新的线程,然后该线程会运行run()方法

      thread.run():就是单纯的调用run()方法,不会开启一个新的线程

    3,了解yield():

      将线程从运行转到可运行状态(就绪态),放弃了当前cpu资源,进入就绪态,然后和其他线程一起去竞争cpu资源

    4,线程的优先级

      设置线程的优先级:thread.setPriority(int priority); 优先级:1-10
      注意:理论上来说,线程的优先级越高,就越先被执行。有些操作系统会忽略我们设置的优先级。所以了解就行

    5,守护线程

      和主线程共死的(主线程退出,守护线程也会退出。比如垃圾回收线程)

    public class DaemonThread {
        private static class UseThread extends Thread{
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName()+"i am daemon Thread");
                }
            }
        }
        public static void main(String[] args)throws InterruptedException {
            Thread endThread = new UseThread();
            /**
             * 注意:必须在start()方法之前,
             *       如果主线程执行完毕,守护线程也停止.
             */
            endThread.setDaemon(true);
            endThread.start();
            Thread.sleep(1);
            /**
             * 在main主线程睡眠的这一1ms期间,守护线程一直在运行,当main线程执行完毕,守护线程也退出
             * 打印结果:
             *  Thread-0i am daemon Thread
                Thread-0i am daemon Thread
                Thread-0i am daemon Thread
                ...
             */
        }
    }

    四,线程间的共享

    1,什么是线程间的共享

    2,如何实现线程间的共享

    2.1,synchronized内置锁

    分为类锁和对象锁:

      对象锁:锁代码块,锁方法
      类锁:static+synchronized : public static synchronized void method(){}

    能不能同时运行:

      同一个对象锁,不能同时运行
      两个不同的对象锁,可以同时运行
      一个类锁和一个对象锁,可以同时运行
      两个类锁,不能同时运行。 因为每个类的Class对象只有一个,所以还是同一把锁。

    区别:

      对象锁,锁的是new出来的对象实例
      类锁,锁的是每个类的Class对象

    2.2,volatile关键字:最轻量的同步机制

      保证了可见性:修改了数据后,强制把数据刷到主内存。读取数据时,强制从主内存中读取数据。
      但是不保证原子性:即 a = a+1;不是一步完成的。操作系统会执行好几条指令才完成加1操作。

    Volatile的使用场景:
    只有一个线程写,多个线程读。

    2.3,ThreadLocal的使用

    每个线程只是用自己线程的变量,把数据和当前线程绑定

    案例:我在当前线程中保存该用户的信息,那么在用户的请求线程进入程序时把用户的信息和当前线程绑定,当用户的请求线程结束,我再把这个线程移除。

    如何实现:

    public class RequestHolder {
    
        private static final ThreadLocal<String> userHolder = new ThreadLocal<>();
    public static void add(String sysUser) {
            userHolder.set(sysUser);
        }public static String getCurrentUser() {
            return userHolder.get();
        }public static void remove() {
            userHolder.remove();
        }
    }

    在拦截器的preHandle()方法中把线程和用户信息绑定,在postHandle()方法中把该线程销毁

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            RequestHolder.add(authorization);//这里我绑定的是token
            return true;
        }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            // 正常返回时, 显式回收threadLocal里的信息
            RequestHolder.remove();
    
        }

    在我需要获取用户信息的地方(比如Service中的方法),通过线程对应的用户信息

    String token  = RequestHolder.getCurrentUser();

    五,线程间的协作

    1,轮询:难以保证及时性,资源开销很大

    2,等待和通知

    等待和通知的标准范式:

      等待方:
        要去获取对象的锁,
        然后在循环里判断条件是否满足,不满足调用wait方法。
        条件满足,执行业务逻辑
      通知方:
        获取对象的锁
        改变条件
        通知所有等待在对象的线程

    3,方法:

      wait():等待着获取对象的锁
      wait(1000):等待超时,超过一定时间就不等待了。
      notify:通知一个线程
      notifyAll:通知所有等待同一把锁的线程

    4,join()方法
    面试问题:有线程A和线程B,如何保证线程B一定在线程A执行完以后才执行?
      方法一:join()
      方法二:countDownLatch
    解释:如果线程A执行了线程B的join方法,线程A必须等待线程B执行完了以后,线程A才能继续自己的工作。

    5,yield(),sleep(),wait(),notify()等方法对锁的影响
      线程在执行yield()以后,持有的锁是不释放的
      sleep()方法调用以后,持有的锁是不释放的
      wait():在调用wait()方法之前,必须要持有锁。在调用wait()方法以后。锁就会被释放(虚拟机进行释放),当wait方法返回时,线程会重新持有锁
      notify():在调用之前,必须要持有锁。调用notify()方法本身是不会释放锁的,只有synchronized代码块执玩才释放锁
      notifyAll():同notify()
    比如:public synchronized void changeKm(){
        this.km = 101;
        notify();//当执行完这行代码时,此时还没有释放锁。
        System.out.println("处理业务逻辑"); //执行完这一行代码后,才释放锁。
       }

  • 相关阅读:
    Thinkphp3.2 cms之角色开发
    说几个你知道的设计模式?
    9种实现点击一个链接弹出一个小窗口的代码
    分享自己作为一个程序员的找工作经历
    网页设置锚点
    博客园网摘地址
    PHP面试总结
    简单的10秒倒计时
    PHP测试题目
    关键字搜索内容总结
  • 原文地址:https://www.cnblogs.com/inspred/p/11096591.html
Copyright © 2011-2022 走看看