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("处理业务逻辑"); //执行完这一行代码后,才释放锁。
       }

  • 相关阅读:
    【PAT甲级】1043 Is It a Binary Search Tree (25 分)(判断是否为BST的先序遍历并输出后序遍历)
    Educational Codeforces Round 73 (Rated for Div. 2)F(线段树,扫描线)
    【PAT甲级】1042 Shuffling Machine (20 分)
    【PAT甲级】1041 Be Unique (20 分)(多重集)
    【PAT甲级】1040 Longest Symmetric String (25 分)(cin.getline(s,1007))
    【PAT甲级】1039 Course List for Student (25 分)(vector嵌套于map,段错误原因未知)
    Codeforces Round #588 (Div. 2)E(DFS,思维,__gcd,树)
    2017-3-9 SQL server 数据库
    2017-3-8 学生信息展示习题
    2017-3-5 C#基础 函数--递归
  • 原文地址:https://www.cnblogs.com/inspred/p/11096591.html
Copyright © 2011-2022 走看看