Java 之多线程知识小结

本贴最后更新于 2695 天前,其中的信息可能已经时移世易

Java 线程相关

  1. 如何创建线程(两种方式,区别,使用场景)
  2. 线程状态调度
  3. 多线程数据共享(会有什么问题,如何实现共享,多线程操作同一个变量会有什么问题,如果不希望有问题怎么做)
  4. 数据传递
  5. 线程池相关(如何创建线程池,要注意什么(初始化线程内部变量),几种常用的使用方式)

1. 线程创建

通常创建线程有两种方式,一个是继承 Thread, 一个是实现 Runnable; 下面则分别实现以做演示,然后说一下这两种的区别,应该如何选择

创建线程

创建线程和使用的一个小 case 如下, 注意的是线程启动是调用 start 方法, 而不是 run 方法; 其次实现 Runnable 接口的类,启动依然是放在一个 Thread 对象中

public class ThreadCreate {


    /**
     * 通过继承  Thread 方式来创建一个新的线程
     */
    public static class ThreadExtend extends Thread {

        @Test
        public void run() {
            System.out.println("new extend thread");
        }
    }


    /**
     * 通过实现 Runnable 方式来创建一个线程
     */
    public static class RunnableImplement implements Runnable {

        @Override
        public void run() {
            System.out.println("new runnable thread");
        }
    }

    @Test
    public void testCreate() {
        new ThreadExtend().start();

        new Thread(new RunnableImplement()).start();

        System.out.println("main!");
    }

}

两种方式对比

为什么会有两种方式呢?这两种的区别何在?

  1. 实现是可以有多个的,但是继承只能有一个父类
  2. 查看 Runnable 的使用方法,最终是放在一个 Thread 里面去执行的,所以在多个相同的程序代码处理一个资源时,这个还是有优势的;但是查看 Thread 实际上就是 Runnable 的实现,同样可以将一个自定义的 Thread 对象,创建多个 Thread 对象来调用

通过上面的描述可以知道一点,如果你希望数据多线程内共享,不妨考虑实现 Runnable 接口(当然继承 Thread 也是 ok 的);如果希望隔离,则不妨考虑继承 Thread (实际上使用 Runnable 接口的实现也是 ok 的,多创建几个实现类接口对象而已,每个对象放在一个新的 Thread 中执行)

按照个人的理解,网上说的实现 Runnable 方便资源共享,更多的是倾向于代码的共享,通常是一个 Runnable 对象,放在多个 Thread 实例中执行;而继承 Thead 类,从出发点来看,继承的一般是作为一个独立线程来执行使用,如果你真要像下面这么做,也不会报错,也能正常运行,只是有点违反设计理念而已

MyThread extreds Thread {...};
MyThread mythread = new MyThread();
new Thread(mythread).start();

case 举例

举一个例子,车站卖票,假设现在有三个窗口,总共只有 30 张车票,卖完就不卖了,怎么实现?如果每个窗口有 10 张车票,各个窗口把自己的卖完了就不卖了,怎么实现?

第一个 case,符合数据共享的一种场景,那么我们的实现可以如下:

public static class TotalSaleTick implements Runnable {
   private int total = 30;

   @Override
   public void run() {
       while (true) {
           if (total > 0) {
               System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total);
           } else {
               break;
           }
       }
   }
}


@Test
public void testTotalSale() {
   TotalSaleTick totalSaleTick = new TotalSaleTick();
   Thread thread1 = new Thread(totalSaleTick, "窗口1");
   Thread thread2 = new Thread(totalSaleTick, "窗口2");
   Thread thread3 = new Thread(totalSaleTick, "窗口3");

   thread1.start();
   thread2.start();
   thread3.start();
   System.out.println("master over!");
}

输出如下, 基本上每次跑的输出结果都不一样, 可以看出的一点是三个窗口售出的票数不同,一个问题,上面这种情况,可能造成超卖么?

窗口1售出一张,剩余:29
master over!
窗口2售出一张,剩余:28
窗口2售出一张,剩余:25
窗口2售出一张,剩余:24
窗口1售出一张,剩余:27
窗口3售出一张,剩余:26
窗口1售出一张,剩余:22
窗口1售出一张,剩余:20
窗口1售出一张,剩余:19
窗口1售出一张,剩余:18
窗口1售出一张,剩余:17
窗口1售出一张,剩余:16
窗口1售出一张,剩余:15
窗口1售出一张,剩余:14
窗口1售出一张,剩余:13
窗口1售出一张,剩余:12
窗口1售出一张,剩余:11
窗口1售出一张,剩余:10
窗口2售出一张,剩余:23
窗口2售出一张,剩余:8
窗口2售出一张,剩余:7
窗口2售出一张,剩余:6
窗口2售出一张,剩余:5
窗口2售出一张,剩余:4
窗口1售出一张,剩余:9
窗口3售出一张,剩余:21
窗口3售出一张,剩余:1
窗口3售出一张,剩余:0
窗口1售出一张,剩余:2
窗口2售出一张,剩余:3

第二个 case,则显然更倾向于继承 Thread 来实现了

public static class SplitSaleTick extends Thread {
   private int total = 10;

   public SplitSaleTick(String name) {
       super(name);
   }

   @Override
   public void run() {
       while (true) {
           if (total > 0) {
               System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total);
           } else {
               break;
           }
       }
   }
}


@Test
public void testSplitSaleTick() {
   SplitSaleTick splitSaleTick1 = new SplitSaleTick("窗口1");
   SplitSaleTick splitSaleTick2 = new SplitSaleTick("窗口2");
   SplitSaleTick splitSaleTick3 = new SplitSaleTick("窗口3");

   splitSaleTick1.start();
   splitSaleTick2.start();
   splitSaleTick3.start();
   System.out.println("master over");
}


/**
* 继承 Thread 也可以实现共享, 只不过比较恶心而已
*/
@Test
public void testSplitSaleTick2() {
   SplitSaleTick splitSaleTick1 = new SplitSaleTick("saleTick");

   Thread thread1 = new Thread(splitSaleTick1, "窗口1");
   Thread thread2 = new Thread(splitSaleTick1, "窗口2");
   Thread thread3 = new Thread(splitSaleTick1, "窗口3");

   thread1.start();
   thread2.start();
   thread3.start();
}

输出接过如下, 三个窗口可以并发卖,且每个窗口卖 10 张,卖完即止

窗口1售出一张,剩余:9
窗口2售出一张,剩余:9
窗口2售出一张,剩余:8
窗口1售出一张,剩余:8
窗口1售出一张,剩余:7
窗口1售出一张,剩余:6
窗口1售出一张,剩余:5
窗口1售出一张,剩余:4
窗口2售出一张,剩余:7
窗口1售出一张,剩余:3
窗口1售出一张,剩余:2
窗口1售出一张,剩余:1
窗口1售出一张,剩余:0
窗口3售出一张,剩余:9
窗口3售出一张,剩余:8
窗口3售出一张,剩余:7
窗口3售出一张,剩余:6
窗口3售出一张,剩余:5
窗口3售出一张,剩余:4
窗口3售出一张,剩余:3
窗口3售出一张,剩余:2
窗口3售出一张,剩余:1
窗口3售出一张,剩余:0
master over
窗口2售出一张,剩余:6
窗口2售出一张,剩余:5
窗口2售出一张,剩余:4
窗口2售出一张,剩余:3
窗口2售出一张,剩余:2
窗口2售出一张,剩余:1
窗口2售出一张,剩余:0


---- test2 输出 ----
窗口1售出一张,剩余:9
窗口1售出一张,剩余:6
窗口1售出一张,剩余:5
窗口1售出一张,剩余:4
窗口1售出一张,剩余:3
窗口1售出一张,剩余:2
窗口3售出一张,剩余:7
窗口2售出一张,剩余:8
窗口3售出一张,剩余:0
窗口1售出一张,剩余:1

2. 线程状态(线程生命周期)

线程创建之后,即调用了 start 方法之后,线程是否开始运行了?这个运行过程是否会暂停呢?如果需要暂停应该怎么办;如果一个线程依赖另一个线程的计算结果,又该如何处理?

image.png

  • 创建:新建一个线程对象,如 Thread thd=new Thread()
  • 就绪:创建了线程对象后,调用了线程的 start() 方法(此时线程知识进入了线程队列,等待获取 CPU 服务 ,具备了运行的条件,但并不一定已经开始运行了)
  • 运行:处于就绪状态的线程,一旦获取了 CPU 资源,便进入到运行状态,开始执行 run()方法里面的逻辑
  • 终止:线程的 run() 方法执行完毕,或者线程调用了 stop() 方法,线程便进入终止状态
  • 阻塞:一个正在执行的线程在某些情况系,由于某种原因而暂时让出了 CPU 资源,暂停了自己的执行,便进入了阻塞状态,如调用了 sleep() 方法
  • 线程让步: join 等待其他线程终止。在当前线程中调用另一个线程的 join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态

3. 方法说明

一个 Thread 实例有一些常用的方法如: start, sleep, run, yield, join, wait 等, 这些方法是干嘛用的,什么场景下使用,使用时需要注意些什么?

方法的执行,将对应线程状态进行说明

run 方法

run 方法中为具体的线程执行的代码逻辑,一般而言,都不应该被直接进行调用

无论我们采用哪种方法创建线程,基本上都是要重写 run 方法,这个方法会在线程执行时调用

start 方法

执行该方法之后,线程进入就绪状态,对使用者而言,希望线程执行就是调用的这个方法(注意调用之后不会立即执行)

这个方法的主要目的就是告诉系统,我们的线程准备好了,cpu 有空了赶紧来执行我们的线程

sleep 方法

睡眠一段时间,这个过程中不会释放线程持有的锁, 传入 int 类型的参数,表示睡眠多少 ms

让出 CUP 的使用、目的是不让当前线程独自霸占该进程所获的 CPU 资源,以留一定时间给其他线程执行的机会

我们最常见的一种使用方式是在主线程中直接调用 Thread.sleep(100) , 表示先等个 100ms, 然后再继续执行

wait 方法

wait()方法是 Object 类里的方法;当一个线程执行到 wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问

wait()使用 notify 或者 notifyAlll 或者指定睡眠时间来唤醒当前等待池中的线程

通常我们执行 wait 方法是因为当前线程的执行,可能依赖到其他线程,如登录线程中,若发现用户没有注册,则等待,等用户注册成功后继续走登录流程(我们不考虑这个逻辑是否符合实际),

这里就可以在登录线程中调用 wait 方法, 在注册线程中,在执行完毕之后,调用 notify 方法通知登录线程,注册完毕,然后继续进行登录后续 action

yield 方法

暂停当前正在执行的线程对象,并执行其他线程

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中

这个方法的执行,有点像一个拿到面包的人对另外几个人说,我把面包放在桌上,我们从新开始抢,那么下一个拿到面包的还是这些人中的某个(大家机会均等)

想象不出啥时候会这么干

join 方法

启动线程后直接调用,即 join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了 join()方法后面的代码,只有等到子线程结束了才能执行

从上面的描述也可以很容易看出什么场景需要调用这个方法,主线程和子线程谁先结束不好说,如果主线程提前结束了,导致整个应用都关了,这个时候子线程没执行完,就呵呵了;其次就是子线程执行一系列计算,主线程会用到计算结果,那么就可以执行这个方法,保证子线程执行完毕后再使用计算结果

4. 数据共享

多线程间数据共享,当多线程公用一个 Runnable 对象时,这个对象中的成员变量即可以达到数据共享的目的;多线程采用不同的 Runnable 对象时,数据怎么共享

公用 Runnable 对象时

上面的售票例子中,其实就有这个场景,上面提出了一个问题,是否会出现超卖的情况?

  1. 因为我们知道 ++ 不是原子操作, 实际可以拆分为三步:

    • 内存到寄存器
    • 寄存器自增
    • 写回内存

    假设 num 为 10 时, 线程 A 和线程 B 都调用 ++num 操作;对于内存到寄存器这一步,两个线程都到了这一步,A 自增将 11 写回内存,B 也进行自增将 11 写会内存,这个时候就少 +1 了

  2. 读一个 long,double 类型的共享变量时,也不是原子操作,在 32 位操作系统上对 64 位的数据的读写要分两步完成,每一步取 32 位数据,如果有两个线程同时写一个变量内存,一个进程写低 32 位,而另一个写高 32 位,这样将导致获取的 64 位数据是失效的数据

在多线程中,共享数据的获取 or 更新,请确保是原子操作;可以考虑同步锁(synchronized)修改共享变量,共享变量前添加 volatile, 使用原子数据类型 AtomicInteger

修改上面的售票代码如下

public static class TotalSaleTick implements Runnable {
        private int total = 30;

        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (total > 0) {
                        System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total);
                    } else {
                        break;
                    }
                }
            }
        }
    }

一个小疑惑,在实际的测试中,即便是上面不加上同步块,好像也没有出问题,对于上面的操作可能运行很多遍都是正确的, 好像和我们预期的不相符,有没有可能是因为总数太少,导致冲突的机率变小了?

private AtomicInteger count = new AtomicInteger(0);
private int sum = 3000;

public class MyThread extends Thread {
   public void run() {
       while (true) {
           if (sum > 0) {
               count.addAndGet(1);
               --sum;
           }else {
               break;
           }
       }
       System.out.println(Thread.currentThread().getName() + " over " + sum);
   }
}


@Test
public void testAdd() throws InterruptedException {
   MyThread myThread1 = new MyThread();
   MyThread myThread2 = new MyThread();

   myThread1.start();
   myThread2.start();

   myThread1.join();
   myThread2.join();

   System.out.println("num: " + sum + " count: " + count.get());
}

对上面的场景,多运行几次,发现输出结果果然是超卖了

Thread-1 over -1
Thread-0 over -1
num: -1 count: 3008

非公用的 Runnable 对象时

共享全局变量 + 共享局部变量两种情况,有点区别

上面的 case 就是一个共享全局变量的 demo,上面出现了并发冲突,可以如下解决, 针对类进行加锁

public class ThreadShareTest {
    private AtomicInteger count = new AtomicInteger(0);
    private int sum = 3000;

    public class MyThread extends Thread {
        public void run() {
            while (true) {
                if (sum > 0) {
                    synchronized (ThreadShareTest.class) {
                        if (sum > 0) {
                            count.addAndGet(1);
                            --sum;
                        }
                    }
                }else {
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " over " + sum);
        }
    }


    @Test
    public void testAdd() throws InterruptedException {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.start();
        myThread2.start();

        myThread1.join();
        myThread2.join();

        System.out.println("num: " + sum + " count: " + count.get());
    }
}

共享局部变量,需要注意的是局部变量要求是 final, 所以下面的 int 采用了数组的形式(基本类型无法修改,引用类型可以改其内部的值, 不能改引用)

@Test
public void testAdd2() throws InterruptedException {
   final int[] num = {3000};
   final AtomicInteger c = new AtomicInteger(0);


   Runnable runnable = new Runnable() {
       @Override
       public void run() {
           while (true) {
               if (num[0] > 0) {
                   c.addAndGet(1);
                   num[0]--;
               } else {
                   break;
               }
           }

           System.out.println(Thread.currentThread().getName() + " over " + num[0]);
       }
   };

   Thread thread1 = new Thread(runnable);
   Thread thread2 = new Thread(runnable);

   thread1.start();
   thread2.start();

   thread1.join();
   thread2.join();

   System.out.println("num: " + num[0] + " count: " + c.get());
}

多运行几次,输出如下,说明也存在并发的问题了, 修正方式同样是加锁

Thread-0 over -1
Thread-1 over -1
num: -1 count: 3001

修改后的 run 方法内部如下

while (true) {
     if (num[0] > 0) {
         synchronized (this) {
             if (num[0] > 0) {
                 c.addAndGet(1);
                 num[0]--;
             } else {
                 break;
             }
         }
     } else {
         break;
     }
 }

线程数据隔离

上面是数据在多线程中共享,很容易出现的就是并发问题;还有一个场景就是我希望不存在数据共享,线程操作的内部变量不影响其他的线程; 最简单的想法就是一个继承了 Thread 的类,其内部类正常来讲就是隔离的,只要你不把它当成 Runnable 接口的使用方式就行

使用 ThreadLocal 来保证变量在线程之间的隔离, 下面是一个简单的演示,两个线程都是在修改 threadLocal 中的值, 但是两个线程的修改,对彼此而言是独立的

public static class LocalT implements Runnable {
   ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

   @Override
   public void run() {
       int start = (int) (Math.random() * 100);
       for (int i =0 ; i < 100; i = i+2) {
           threadLocal.set(start + i);
           System.out.println(Thread.currentThread().getName() + " : " + get());
       }
   }

   public int get() {
       return threadLocal.get();
   }
}


@Test
public void testLocal() throws InterruptedException {
   LocalT local = new LocalT();

   Thread thread1 = new Thread(local);
   Thread thread2 = new Thread(local);

   thread1.start();
   thread2.start();

   thread1.join();
   thread2.join();
}

5. 数据传递

数据如何传递给线程,有如何把线程计算的结果抛出来

传递数据

比较容易想到的就是在创建对象时,传入数据;或者调用线程对象的 setXXX 方法传入数据, 当做正常的对来操作处理即可

需要注意的是,在线程的执行期间,你修改了其中的局部变量,会出现什么情况呢?

public static class ThreadData implements Runnable {
   private int num = 0;


   public void run() {
       while (num < 100) {
           System.out.println(Thread.currentThread().getName() + " now: " + num++);
       }

       System.out.println(Thread.currentThread().getName() + " num: " + num);
   }

   public void setNum(int num) {
       System.out.println(this.num + " now set to " + num);
       this.num = num;
   }
}


@Test
public void testThreadSetData() throws InterruptedException {
   ThreadData threadData = new ThreadData();

   Thread thread1 = new Thread(threadData);
   Thread thread2 = new Thread(threadData);

   thread1.start();
   thread2.start();

   threadData.setNum(200);

   thread1.join();
   thread1.join();
}

输出如下, 将 num 设置为 200 之后,并没有如我们预期的结束线程,依然在往下走, 这里就相当于是有一个你修改了这个数据,是否会立马就生效呢?特别是对其他的线程而言

...
Thread-1 now: 24
Thread-1 now: 25
Thread-0 now: 14
26 now set to 200
Thread-0 now: 27
Thread-0 now: 28
Thread-1 now: 26
Thread-0 now: 29
Thread-1 now: 30
....

输出结果

线程执行了一个任务之后,输出的结果可以怎么处理

一个实例,一个线程实现累加的过程,我现在希望实现 1 加到 100, 开四个线程,怎么做?

下面是一个实现,不知道有没有什么问题

public static class CalculateThread extends Thread {

   private int start;
   private int end;

   private int ans;

   public CalculateThread(int start, int end) {
       this.start = start;
       this.end = end;
   }

   public void run() {
       for (int i = start; i <= end; i++) {
           ans += i;
       }
   }

   public int getAns() {
       return ans;
   }
}


@Test
public void testCalculate() throws InterruptedException {
   CalculateThread c1 = new CalculateThread(1, 25);
   CalculateThread c2 = new CalculateThread(26, 50);
   CalculateThread c3 = new CalculateThread(51, 75);
   CalculateThread c4 = new CalculateThread(76, 100);

   c1.start();
   c2.start();
   c3.start();
   c4.start();


   c1.join();
   c2.join();
   c3.join();
   c4.join();

   System.out.println("ans1: " + c1.getAns() + " ans2: " + c2.getAns() + " ans3: " + c3.getAns() + " ans4: " + c4.getAns());
   int ans = c1.getAns() + c2.getAns() + c3.getAns() + c4.getAns();
   System.out.println("ans : " + ans);
}

6. 线程池

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力
线程的频繁创建和销毁可能浪费大量的时间,线程池就是为了解决这个问题而产生

创建

通过 ThreadPoolExecutor 来创建一个线程池

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

参数说明:

  • corePoolSize 线程池中任务的基本个数
    • 新提交一个任务时,若线程池中个数未达到基本个数,则新建一个线程
    • 到线程池中的线程数达到基本个数时,再提交任务,则看是否有空闲线程,有则只直接使用
    • 若无空闲线程,则新几条的任务放入排队
  • maximumPoolSize 线程 chi 池中任务的做多个数
    • 当线程池中个数达到 corePoolSize & 且队列排满了
    • 新创建线程来执行任务
    • 当线程池中任务达到 maximumPoolSize,则不再创建
  • keepAliveTime 线程池的工作线程空闲后存活的时间
  • milliseconds 配合上个参数使用,表示时间的单位,如 TimeUnit.SECONDS
  • runnableTaskQueue 排队队列
    • ArrayBlockingQueue 基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
    • LinkedBlockingQueue 基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue
    • SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue
    • PriorityBlockingQueue 一个具有优先级得无限阻塞队列
  • threadFactory 创建线程的工厂,通常会重新指定线程名,方便 debug
  • handler 线程池饱和策略
    • 当线程数达到 maximumPoolsize 队列已满时,表示饱和
    • CallerRunsPolicy 只用调用者所在线程来运行任务
    • DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务
    • DiscardPolicy 不处理,丢弃掉
    • AbortPolicy 抛异常
    • 也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略

线程池提交任务的处理流程

线程池提交任务的处理流程

提交任务

1. execute(Runnable)

直接执行一个实现了 Runnable 的接口,即表示提交了一个异步任务给线程池

2. submit(Runnable)

相比较于上面的,区别是这个会返回一个 Future<V> 对象,通过调用 future.get() 可以获取线程的返回值,其中这个方程是线程阻塞的,直到返回了结果之后,才会继续执行下去

关闭线程

线程池的 shutdown 或 shutdownNow 方法来关闭线程池

shutdown 的原理是只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程

shutdownNow 的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止

调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。

至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow

线程池的配置

分析

  1. 任务的性质:CPU 密集型任务,IO 密集型任务和混合型任务。
    • CPU 密集型任务配置尽可能少的线程数量,如配置 Ncpu+1 个线程的线程池
    • IO 密集型任务则由于需要等待 IO 操作,线程并不是一直在执行任务,则配置尽可能多的线程,如 2*Ncpu
    • 混合型的任务,如果可以拆分,则将其拆分成一个 CPU 密集型任务和一个 IO 密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解
  2. 任务的优先级:高,中和低。
    • 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理
  3. 任务的执行时间:长,中和短。
    • 执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。
    • 因为线程提交 SQL 后需要等待数据库返回结果,如果等待的时间越长 CPU 空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用 CPU

获取线程数

Runtime.getRuntime().availableProcessors()

实例分析

背景:

实现一个异步的报警 case,首先是有三种报警方式,邮件、微信、短信;其次是具体的报警都是异步处理(一个报警执行的线程池);要求一分钟内报警有上限设置(即要实现报警的计数与清零);报警的重要性根据邮件-》微信-》短信进行递增,当超过每个报警类型的最低阀值时,晋升报警类型

silver-alarm 一个报警的基本框架

源码传送门

参考

  • 线程
    122 引用 • 111 回帖 • 3 关注
  • 安全

    安全永远都不是一个小问题。

    199 引用 • 816 回帖 • 1 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...