美文网首页
并发与多线程开发

并发与多线程开发

作者: 老北瓜 | 来源:发表于2020-09-27 15:09 被阅读0次

    认识线程

      线程的启动方式以及应用场景
      线程的状态和常用方法
      线程的优先级 提升任务的响应速度
      线程间通讯 (子线程与主线程 发消息)

    多线程开发

      线程安全 (关键字synchornized 锁 , 原子类, 并发容器)
      线程流程控制

    线程池原理

      任务调度
      复用原理

    多线程优化

     线程池
     并发安全
     kotlin(协程)

    线程与进程
    分类:ui线程 工作线程

    5种线程创建方式

    • new Thread (2种创建方法)
      缺点: 却反统一管理,可能无限制新建线程,相互之间竞争,可能占用过多系统资源导致OOM 或者死机.
    public class ThreadDemo {
        public static void main(String[] args) {
            Thread t1 = new MyThread();
            
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println("线程2 =" + i);
                    }
                }
            });
    
            t1.start();
            t2.start();
            
        }
    
        static class MyThread extends Thread {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {
                    System.out.println("线程1 = " + i);
                }
            }
        }
    }
    
    • AsyncTask (3种写法)
      于是Android 提供了 AsyncTask, 轻量级任务工具类,提供任务执行的进度回调到UI线程,
      场景:如果需要知道任务执行的进度,多个任务串行
      缺点:生命周期和宿主的生命周期不同不,有可能会导致内存泄漏,默认情况下所有任务都是串行执行。
      解决方法 可以加上static不持有宿主的对象,如果程序不能并发执行,就指定一个线程池.
    public class AsyncTaskDemo  extends AsyncTask<String,Integer,String> {
    
        @Override
        protected String doInBackground(String... params) {
           //TODO 。。。。。
            publishProgress(**);
            return params[0];
        }
    
        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(s);
            // 拿到结果  result
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            // 拿到进度 , values[0];
        }
        
    }
    
    main(){
            AsyncTaskDemo asyncTask = new AsyncTaskDemo();
    
            // #1, 默认串行执行,可以感知任务进度
            asyncTask.execute( params );
            //  可以修改为并发执行
            asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    
    
            // #2,如果不想知道任务执行进度,可以直接创建
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    // ......
                }
            });
    
            //# 3,直接创建 并发执行
            AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
                @Override
                public void run() {
    
                }
            });
    }
    
    • HandlerThread
      使用于主线程需要和工作线程通信,适用于持续性任务,比如轮询,所有任务串行执行。
      缺点: 不会像普通线程一样主动销毁资源,会一直运行,所以可能会造成内存泄漏。
     private void handlerThread() {
            // 创建一个子线程
            HandlerThread thread = new HandlerThread("current-thread");
            thread.start();
            
            /**
             *  在主线程中新建一个Handler,持有 Thread 的 looper
         Looper.
             *  现在是主线程中的Handler 持有了一个 子线程的 Looper
             */
            MHandler handler = new MHandler(thread.getLooper());
    
            // handler 发送数据 ,就是主线程 向子线程发送消息
            handler.sendEmptyMessage(1);
    
    
        }
    
    
        static class MHandler extends Handler {
            public MHandler(@NonNull Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                //  因为 Handler 持有了子线程的 Looper,所以此时实在子线程当中。
                Log.e("MHandler", "message = " + msg.what);
               
            }
        }
    
    
    如果 
      MHandler handler = new MHandler();  // 没有传递Looper
      handler.sendEmptyMessage(1);
      // 因为主线程中创建 Handler 时会有个默认的  Looper
      //这时捕获消息的就是再主线程 中.
    
    • IntentService
      继承与Service ,在创建IntentService时,onCreate()内部会创建一个子线程来完成耗时操作
      应用场景: 适用于任务需要夸页面读取任务执行的进度、结果。比如后台上传图片,批量操作数据库等。任务执行完成后,就会自我结束,不需要手动 stopService()(与Service的区别)
    public class MyIntentService extends IntentService {
    
        public MyIntentService() {
            super("MyIntentService");
        }
    
    
        @Override
        protected void onHandleIntent(Intent intent) {
            if (intent != null) {
                String value = intent.getStringExtra("key");
                Log.e("MyIntentService", "传递 key = " + value);
            }
        }
    
    }
    

    main 中调用

           Intent intent = new Intent(this, MyIntentService.class);
            intent.putExtra("key", "value");
            startService(intent);
    
    
    • ThreadPoolExecutor
      使用与快速处理大量耗时较短的任务场景。
    Executors.newCachedThreadPool();            // 线程可复用的线程池
    Executors.newFixedThreadPool();             // 固定线程数量的线程池
    Executors.newScheduledThreadPool();         // 可指定定时任务的线程池
    Executors.newSingleThreadExecutor();        // 线程数量为1 的线程池.
    

    线程的优先级

    一般而言,线程的优先级越高,获得Cpu片的概率越大。

           /**
             *  Jdk 提供的 , [1,10] 数字越大,优先级越高, UI线程的 优先级 为 5 ;
             *
             */
            Thread thread = new Thread();
            thread.setPriority(1);
    
    
            /**
             * Android API , 优先级更精细的划分为 [-20,19],数字越小,优先级越高, UI线程的 优先级为 -10.
             * 效果较为明显
             * 一般把耗时较长的线程的优先级设置较低级别,
             * 耗时较短较频繁的线程的优先级设置较高级别. 但是不要高于UI 线程.
             */
             android.os.Process.setThreadPriority();
    

    线程的状态及常用方法

    image.png

    wait()/notify()
      当前线程进入等待状态,并且释放资源对象锁,可使用notify(),notifyAll() 或者等待超时来唤醒.
    使用与多线程同步,一个线程要等待另一个线程的结果或者部分结果。 注意 wait - notify 的执行顺序

     final Object object = new Object();
            class Runnable1 implements Runnable {
                @Override
                public void run() {
                    Log.e("Runnable", "thread1 - start");
    
                    synchronized (object) {
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Log.e("Runnable", "thread1 - end");
                }
            }
    
            class Runnable2 implements Runnable {
    
                @Override
                public void run() {
                    Log.e("Runnable", "thread2 - start");
                    
                    synchronized (object) {
                        object.notify();
                    }  
                    
                    /**
                     *  线程2 notify()唤醒了线程1 ,
                     *  如果线程2有耗时操作,就先执行线程1 了.
                     */
              //      try {
              //          Thread.sleep(1000);
               //     } catch (InterruptedException e) {
               //         e.printStackTrace();
               //     }
                   
                    Log.e("Runnable", "thread2 - end");
                }
            }
            new Thread(new Runnable1()).start();
            new Thread(new Runnable2()).start();
    

    以上, object就是对象锁, 这里如果换成 this , 跟object有啥不同吗。 wait()的使用是要在 synchornized() 同步代码块里面执行的.

    理想状态下,线程1 执行了 wait()以后使当前线程进入等待状态,于是线程2拿到Cpu片开始执行,然后通过notify()唤醒线程1。
    但是实际情况,线程1 和线程2 谁先执行可不知道,如果线程2先执行了,轮到线程1执行时,到了wait() 就没有唤醒了出现假死现象。

    为了规避这种情况,可以定义一个 volatile boolean hasNotify = false, 线程2 notify() 以后 hsNotify = true ; 在 线程1 wait()前判断 if(!hasNotify){ wait() } , 或者 wait(1000), 自动唤醒,可以避免线程 一直 wait();

    join()
        在当前线程中插入一条任务,当任务完成以后线程才可以继续执行.

     class JoinThread extends Thread {
                @Override
                public void run() {
                    super.run();
                    Log.e("JoinThread", "run: 1 " + System.currentTimeMillis());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.e("JoinThread", "run:2 " + System.currentTimeMillis());
                }
            }
    
            JoinThread thread = new JoinThread();
            thread.start();
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Log.e("JoinThread", "join: 3 " + System.currentTimeMillis());
    }
    执行顺讯 就是  1,2,3
    UI线程中插入了一个 JoinThread,UI线程要等JoinThread执行完毕以后才接着执行。
    

    yield()
       不咋的用
    暂停当前正在执行的线程对象,不会释放资源锁。
    sleep()
       使调用线程进入休眠状态,但在一个synchornized块中执行sleep,线程虽然休眠,但不会释放资源对象锁.

     final Object object = new Object();
            class Runnable1 implements Runnable {
                @Override
                public void run() {
                    Log.e("Runnable", "thread1 - start");
    
                    synchronized (object) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Log.e("Runnable", "thread1 - end");
                }
            }
    
            class Runnable2 implements Runnable {
                @Override
                public void run() {
                    synchronized (object) {
                        Log.e("Runnable", "thread2 - start");
                        Log.e("Runnable", "thread2 - end");
                    }
                }
            }
            new Thread(new Runnable1()).start();
            new Thread(new Runnable2()).start();
    

    线程1 和线程2 的执行顺序不保证,如果线程1先执行,线程2会等线程1全部执行完才执行.

    线程间通信

    子线程发主线程发送消息
          post() ,runOnUiThread()...

    主线程向子线程发送消息

     /**
             * 要让主线程给子线程发送消息, 必然要使子线程有处理消息的能力 ,Looper
             */
            class LooperThread extends Thread {
                Looper looper;
    
                public LooperThread(String name) {
                    super(name);
                }
    
                // 但是由于 Thread 可能不会一 start 就执行 run(),
                // 所以 looper可能为空
                // 于是  wait();
                public Looper getLooper() {
                    if (looper == null && isAlive()) {
                        synchronized (this) {
                            try {
                                wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    return looper;
                }
    
                @Override
                public void run() {
                    Looper.prepare();
    
                    looper = Looper.myLooper();
                    synchronized (this) {
                        notify();
                    }
                    // 开启Looper 的无线循环;
                    Looper.loop();
                }
            }
    
    
            LooperThread looperThread = new LooperThread("Looper - Thread");
            looperThread.start();
    
            // 创建的 Handler 持有了子线程的Looper, handler 就会捕获有子线程中Looper分发的消息.
            Handler handler = new Handler(looperThread.getLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
    
                    Log.e("ThreadMessage", " message =  " + msg.what);
                    Log.e("ThreadMessage", "thread - name: " + Thread.currentThread().getName());
                }
            };
    
            handler.sendEmptyMessage(2);
    

    线程安全

    (买票)
    线程安全的本质:

        能够让并发线程,有序的运行(有序可能是先来后到的排队,也可能是有人插队,但同一时刻只能有一个线程有权访问同步数据), 线程执行的结果,能够对其他线程可见。

    AutomicInteger 原子类

        实现无锁数据更新,自旋( do-while 循环 ,线程不会阻塞) 的设计能够有效避免线程阻塞-唤醒的系统资源开销。 客户端并发量不会很高.
    适用于多线程技术,原子操作,并发量小的场景。

    volatile 可见性修饰

        volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存
    不能解决非原子操作的线程安全性,性能不及原子类高。

     volatile int count;
        private void increment() {
    
            // 其他线程可见.
            count = 5;
    
            // 以下两步操作都不是一步操作,是非原子操作,所以不可被其他线程可见.
            count = count + 1;
            count++;
        }
    

    对比两种方式

    public class AtomicDemo {
    
        public static void main(String[] args) {
    
            final AtomicTask task = new AtomicTask();
    
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
    
                    for (int i = 0; i < 10000; i++) {
                        task.atoAdd();
                        task.volatileCount();
                    }
    
                }
            };
    
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
    
            t1.start();
            t2.start();
    
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("原子类操作的结果 = " + task.atomicInteger.get()); // 结果  20000
            System.out.println("volatile 操作的结果 = " + task.count);   // 结果 18720       
        }
        
        static class AtomicTask {
            AtomicInteger atomicInteger = new AtomicInteger();
            volatile int count = 0;
            public void atoAdd() {
                // 增量 ,先 加1,然后返回操作之前的值.
                atomicInteger.getAndIncrement();
    
                // 减量 ,先获取未更改前的值,然后 减1
                //atomicInteger.getAndDecrement();
            }
            public void volatileCount() {
                count++;
            }
        }
    }
    

    以上,在输出结果中表现 使用volatile 修饰的变量 不保证线程安全。

    synchornized

    锁java对象, 锁Class类对象,锁代码块.

    • 锁方法 , 加在方法上,为获取到对象锁的其他线程不可以访问该方法
    public class ThreadDemo {
    
        static ArrayList<String> tickets = new ArrayList<>();
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                tickets.add("票 -" + (i + 1));
            }
            saleTicket();
        }
        
        private static void saleTicket() {
            final SynchronizedDemo demo = new SynchronizedDemo();
            for (int i = 0; i < tickets.size(); i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        demo.buyTicket();
                    }
                }).start();
            }
        }
    
        static class SynchronizedDemo {
            synchronized void buyTicket() {
                String name = Thread.currentThread().getName();
                System.out.println("买票人:" + name + " 已经准备好");
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
            }
    
        }
    }
    ==========  输出结果 ============
    买票人:Thread-0 已经准备好
    买票人 :Thread-0 买到的票是 票 -1
    买票人:Thread-4 已经准备好
    买票人 :Thread-4 买到的票是 票 -2
    买票人:Thread-3 已经准备好
    买票人 :Thread-3 买到的票是 票 -3
    买票人:Thread-2 已经准备好
    买票人 :Thread-2 买到的票是 票 -4
    买票人:Thread-1 已经准备好
    买票人 :Thread-1 买到的票是 票 -5
    

    以上, 针对同一个 买票人对象,在butTicket()方法上加 synchronized就可以保证线程安全. 如果在多线程中访问不同对象的同步方法,就不能保证线程安全了.
    因为线程同步是对同一个对象而言.
    比如这样, 每个线程中都创建一个买票人对象,

    for (int i = 0; i < tickets.size(); i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        final SynchronizedDemo demo = new SynchronizedDemo();
                        demo.buyTicket();
                    }
                }).start();
            }
    
    ==========  输出结果 ============
    买票人 :Thread-3 买到的票是 票 -2
    买票人 :Thread-0 买到的票是 票 -1
    买票人 :Thread-2 买到的票是 票 -4
    买票人 :Thread-1 买到的票是 票 -3
    买票人 :Thread-4 买到的票是 票 -1
    
    =========  == 购买的票出现了重复 =====
    
    • 锁Class 对象, 加载 static 方法上相当于给Class对象加锁,哪怕是不同的Java对象实例,也需要排队等候.
      如果 synchronized 加在 static方法上,线程安全
     static class SynchronizedDemo {
            synchronized static void buyTicket() {
                String name = Thread.currentThread().getName();
                System.out.println("买票人:" + name + " 已经准备好");
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
            }
        }
    =========== 输出结果 ==========
    买票人:Thread-0 已经准备好
    买票人 :Thread-0 买到的票是 票 -1
    买票人:Thread-4 已经准备好
    买票人 :Thread-4 买到的票是 票 -2
    买票人:Thread-3 已经准备好
    买票人 :Thread-3 买到的票是 票 -3
    买票人:Thread-2 已经准备好
    买票人 :Thread-2 买到的票是 票 -4
    买票人:Thread-1 已经准备好
    买票人 :Thread-1 买到的票是 票 -5
    

    以上,static加锁,相当于给Class类加锁, 在内存中只有一个,即使不同的买票人对象来访问,也是可以保证线程安全的.

    • 锁代码块,获取到对象锁的线程可以执行同步代码块以外的代码.
     void buyTicket() {
                String name = Thread.currentThread().getName();
    
                synchronized (this) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("买票人 :" + name + "正在买票");
                }
                System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
            }
    ================ 输出结果 ===================
    买票人 :Thread-1正在买票
    买票人 :Thread-1 买到的票是 票 -1
    买票人 :Thread-3正在买票
    买票人 :Thread-3 买到的票是 票 -2
    买票人 :Thread-4正在买票
    买票人 :Thread-4 买到的票是 票 -3
    买票人 :Thread-2正在买票
    买票人 :Thread-2 买到的票是 票 -4
    买票人 :Thread-0正在买票
    买票人 :Thread-0 买到的票是 票 -5
    

    以上 如果 synchronized (SynchronizedDemo.class){ .........买票...... }
    能保证线程同步.

    synchronized 优劣势
    优势:可以规避死锁,即使同步代码快中出现了异常,jvm也能自动释放锁。
    劣势: - 必须要获取锁对象的线程执行完或者异常才可以释放锁,不能中断
          - 多个线程获取锁 是否成功也不知道,不够灵活.
          - 不能设置超时。

    ReentrantLock锁

    void lock() ;                 获取不到会阻塞
    boolean tryLock() ;           尝试获取锁,获取到锁返回 true,
    

    基本用法 :

    public class ReentrantLockDemo {
    
        public static void main(String[] args) {
            final ReentrantTask task = new ReentrantTask();
    
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
    
                        task.buyTicket();
                    }
                }).start();
            }
        }
    
    
        static class ReentrantTask {
            ReentrantLock lock = new ReentrantLock();
    
            void buyTicket() {
                String name = Thread.currentThread().getName();
                
                try {
                    lock.lock();
                    System.out.println(name + " :准备买票");
                    Thread.sleep(100);
                    System.out.println(name + " : 票买好了");
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    =============== 输出结果 ============
    Thread-0 :准备买票
    Thread-0 : 票买好了
    Thread-1 :准备买票
    Thread-1 : 票买好了
    Thread-2 :准备买票
    Thread-2 : 票买好了
    Thread-3 :准备买票
    Thread-3 : 票买好了
    Thread-4 :准备买票
    Thread-4 : 票买好了
    Thread-5 :准备买票
    Thread-5 : 票买好了
    

    可重入,避免死锁
    在不释放锁的情况下,重复获取锁的对象.

                try {
                    lock.lock();
                    System.out.println(name + " :准备买票");
                    Thread.sleep(100);
                    System.out.println(name + " : 票买好了");
    
    
                    lock.lock();
                    System.out.println(name + " :准备买票");
                    Thread.sleep(100);
                    System.out.println(name + " : 票买好了");
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                    lock.unlock();
                }
    
    ==============  输出结果 ==============
    Thread-0 :准备买票
    Thread-0 : 票买好了
    Thread-0 : 又准备买票
    Thread-0 : 票又买好了
    Thread-1 :准备买票
    Thread-1 : 票买好了
    Thread-1 : 又准备买票
    Thread-1 : 票又买好了
    Thread-2 :准备买票
    Thread-2 : 票买好了
    Thread-2 : 又准备买票
    Thread-2 : 票又买好了
    Thread-3 :准备买票
    Thread-3 : 票买好了
    Thread-3 : 又准备买票
    Thread-3 : 票又买好了
    Thread-4 :准备买票
    Thread-4 : 票买好了
    Thread-4 : 又准备买票
    Thread-4 : 票又买好了
    

    公平锁与非公平锁
    公平锁 : 所有进入阻塞队列的线程依次有机会执行.
    (默认)非公平锁:允许线程插队,避免每一个线程都阻塞再被唤醒,性能较高。但是因为可以插队的原因,会导致有的线程一直得不到执行.

    ReentrantLock lock = new ReentrantLock( true / false );
    

    简单模拟多线程打印功能

    public class ReentrantDemo2 {
    
        public static void main(String[] args) {
    
            final PrintTask task = new PrintTask();
    
            for (int i = 0; i < 5; i++) {
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        task.print();
                    }
                }).start();
            }
            
        }
    
    
        static class PrintTask {
            private ReentrantLock lock = new ReentrantLock(true);
    
            void print() {
                String name = Thread.currentThread().getName();
                try {
                    lock.lock();
                    System.out.println(name + ": 第一次打印");
                    Thread.sleep(1000);
                    lock.unlock();
    
    
                    lock.lock();
                    System.out.println(name + ": 第二次打印");
                    Thread.sleep(1000);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
        
    }
    

    如果 公平锁
    输出结果

    Thread-0: 第一次打印
    Thread-1: 第一次打印
    Thread-2: 第一次打印
    Thread-3: 第一次打印
    Thread-4: 第一次打印
    Thread-0: 第二次打印
    Thread-1: 第二次打印
    Thread-2: 第二次打印
    Thread-3: 第二次打印
    Thread-4: 第二次打印
    

    如果非公平锁

    Thread-0: 第一次打印
    Thread-0: 第二次打印
    Thread-1: 第一次打印
    Thread-1: 第二次打印
    Thread-2: 第一次打印
    Thread-2: 第二次打印
    Thread-3: 第一次打印
    Thread-3: 第二次打印
    Thread-4: 第一次打印
    Thread-4: 第二次打印
    

    以上,表明公平锁释放锁以后,每个线程获取到锁的机会是均等的, 如果是非公平锁,当第一次打印完释放锁以后,因为其他线程的状态都是阻塞的,重新唤醒需要有一定的资源消耗,而当前线程释放掉以后还没有进入阻塞状态,由当前线程来获取锁对象是开销最小的。

    应用场景:
    公平锁:交易
    非公平锁 :synchornized, 场景就很多了.

    ReentrantLock- Condintion对象

    使用 await - singnal 可以指定唤醒一个(或一组)线程。

    共享锁 & 排他锁 (ReentrantReadWriteLock)

    共享锁,所有线程都可同时获得,并发量高,比如在线查看文档
    排他锁,同一时刻只能有一个线程有权修改资源,比如在线文档编辑.

      如何正确的使用锁和原子类

    一般情况下 synchronized 已经足够使用,
    如果需要知道锁的细节,就要使用Lock

       调优

    • 减少持锁的时间,比如synchronized 同步代码快,非持有锁的线程可以执行同步代码块之外的程序,可以减少线程等待时间.
    • 锁粗化 , 如果多次同步代码块之间有一部分耗时很短不用同步的代码,可以将这些程序合并为一次同步.
    • 在并发量不大的情况下,可以考虑使用原子类。

    线程池

      为什么引入线程池

    • 降低资源消耗 : 通过重复利用已创建的线程降低线程创建和销毁的造成的消耗。
    • 提高响应速度: 当任务到达时,任务可以不用等待线程的创建就可以立即执行。
    • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会造成系统资源的浪费,还会影响系统的稳定性,使用线程池统一分配,调优和监控.

      Java中默认的线程池

      线程池实现原理

      线程池中线程复用的原理

    相关文章

      网友评论

          本文标题:并发与多线程开发

          本文链接:https://www.haomeiwen.com/subject/gjvnyktx.html