参考1

1、计算一亿个数字之和

使用单线程 、多线程测试计算苏联

  1. public class ThreadF {
  2. //定义1亿长度的数组
  3. private static double[] nums = new double[1_0000_0000];
  4. private static Random random = new Random();
  5. private static double result = 0.0,result1 = 0.0,result2 = 0.0;
  6. //静态代码块 启动先执行 只执行一次
  7. static {
  8. for (int i = 0; i < nums.length ; i++) {
  9. nums[i] = random.nextDouble();
  10. }
  11. }
  12. public static void main(String[] args) throws InterruptedException {
  13. System.out.println("初始化长度:"+ nums.length);
  14. ThreadF.simpleWay();
  15. ThreadF.doubleWay();
  16. }
  17. //使用单线程计算
  18. private static void simpleWay(){
  19. long startTime = System.currentTimeMillis();
  20. for (int i = 0; i < nums.length ; i++) {
  21. result += nums[i];
  22. }
  23. System.out.println("单线程结果:"+ result);
  24. long endTime = System.currentTimeMillis();
  25. long time = endTime - startTime ;
  26. System.out.println("单线程耗时:"+ time );
  27. }
  28. //使用多线程计算
  29. private static void doubleWay() throws InterruptedException {
  30. Thread thread1 = new Thread(()->{
  31. for (int i = 0; i < nums.length/2 ; i++) {
  32. result1 += nums[i];
  33. }
  34. });
  35. Thread thread2 = new Thread(()->{
  36. for (int i = nums.length/2; i < nums.length ; i++) {
  37. result2 += nums[i];
  38. }
  39. });
  40. long startTime = System.currentTimeMillis();
  41. thread1.start();
  42. thread2.start();
  43. //保持顺序 线程1线程2排序
  44. thread1.join();
  45. double res = result1 + result2;
  46. System.out.println("多线程结果:" + res);
  47. long endTime = System.currentTimeMillis();
  48. long time = endTime - startTime;
  49. System.out.println("多线程耗时:" + time);
  50. }
  51. }

运行结果:
image.png

2、多线程实现卖票小程序

某电影院正在上映某大片,共5张票,而现在有3个窗口售卖,请设计一个程序模拟该电影院售票.

  1. public class ThreadD01 extends Thread{
  2. //售卖的电影票数
  3. private int ticket = 5;
  4. //重写run方法
  5. public void run() {
  6. while (true){
  7. System.out.println("当前线程名:" + Thread.currentThread() + "电影剩余票数:" +ticket--);
  8. if (ticket < 0){
  9. break;
  10. }
  11. }
  12. }
  13. public static void main(String[] args) {
  14. ThreadD01 one = new ThreadD01();
  15. ThreadD01 two = new ThreadD01();
  16. ThreadD01 three = new ThreadD01();
  17. one.start();
  18. two.start();
  19. three.start();
  20. }
  21. }

结果分析:从下图输出结果中可以分析,使用继承Thread类实现卖票,导致每个窗口都卖了五张票,而这个电影院总共才五张票,多线程出现了超卖现象。原因是继承Thread方式,是多线程多实例,无法实现资源的共享
image.png

2.1 、优化一

实现Runnable接口进行卖票,电影院总共才五张票,多线程卖票正常。原因是实现Runnable方式,是多线程单实例,可实现资源的共享

  1. public class ThreadD01 extends Thread{
  2. //售卖的电影票数
  3. private static int ticket = 5;
  4. //通过Runnable 方式实现多线程
  5. static class RunnableDemo implements Runnable{
  6. @Override
  7. public void run() {
  8. while (ticket >0) {
  9. show();
  10. }
  11. }
  12. //实现售卖方法
  13. public void show(){
  14. if (ticket>0){
  15. System.out.println("当前售票名称:" + Thread.currentThread() + "剩余票数:" + ticket-- + "张");
  16. }
  17. }
  18. }
  19. public static void main(String[] args) {
  20. //创建两个线程
  21. RunnableDemo r = new RunnableDemo();
  22. new Thread(r).start();
  23. new Thread(r).start();
  24. }
  25. }

程序结果:
image.png

2.2、优化二

细心的小伙伴可能会发现,怎么在2.1输出打印的票数不是从大到小排序的,这跟现实中的卖票场景不一样呐。如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只有一个线程进行,其他线程要等待此线程完成之后才可以继续执行

  1. //实现售卖方法
  2. public synchronized void show(){
  3. if (ticket>0){
  4. System.out.println("当前售票名称:" + Thread.currentThread() + "剩余票数:" + ticket-- + "张");
  5. }
  6. }

程序结果:
image.png

3、线程池应用

参考1 参考2 参考3

公用代码类,为下面测试使用

  1. public class MyThread extends Thread {
  2. private int i;
  3. public MyThread(int in) {
  4. this.i = in;
  5. }
  6. //重写方法
  7. public void run() {
  8. try {
  9. this.sleep(500);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(currentThread().getName()+ "正在打印:" + i);
  14. }
  15. }

3.1、newSingleThreadExecutor

单线程化的线程池

说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。
特点:相当于单线程串行执行所有任务如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行

  1. public static void main(String[] args) {
  2. ExecutorService executorService = Executors.newSingleThreadExecutor();
  3. for (int i = 0; i < 10; i++) {
  4. executorService.execute(new MyThread(i));
  5. }
  6. //关闭线程:停止接收新任务,原来的任务继续执行
  7. executorService.shutdown();
  8. }

程序结果:
image.png

3.2、newFixedThreadPool

定长线程池
说明:初始化一个指定线程数的线程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作为阻塞队列
特点:每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,即使当线程池没有可执行任务时,也不会释放线程。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. public class MyThreadText {
  4. public static void main(String[] args) {
  5. ExecutorService executorService = Executors.newFixedThreadPool(5);
  6. for (int i = 0; i < 10; i++) {
  7. executorService.execute(new MyThread(i));
  8. }
  9. executorService.shutdown();
  10. }
  11. }

程序结果:
image.png

3.3、newCachedThreadPool

缓存线程池
说明:初始化一个可以缓存线程的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
特点:在没有任务执行时,当线程的空闲时间超过keepAliveTime,默认为60s,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. public class MyThreadText {
  4. public static void main(String[] args) {
  5. ExecutorService executorService = Executors.newCachedThreadPool();
  6. for (int i = 0; i < 10; i++) {
  7. executorService.execute(new MyThread(i));
  8. }
  9. executorService.shutdown();
  10. }
  11. }

程序结果:
image.png

3.4、newScheduledThreadPool

定时任务线程池
特定:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。
总结:除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。

  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. public class ScheduledThread extends Thread {
  4. private int i;
  5. public ScheduledThread(int in) {
  6. this.i = in;
  7. }
  8. @Override
  9. public void run() {
  10. while (true) {
  11. try {
  12. this.sleep(2000);
  13. } catch (InterruptedException E) {
  14. E.printStackTrace();
  15. }
  16. //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  17. Date date = new Date();
  18. System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
  19. }
  20. }
  21. }

编写验证的主程序,设置线程池有5个线程可用,:

  1. import java.util.concurrent.ScheduledThreadPoolExecutor;
  2. public class ScheduledThreadTest {
  3. public static void main(String[] args) {
  4. ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(5);//设置线程个数
  5. for (int i = 0; i < 5; i++) {
  6. atpe.execute(new ScheduledThread(i));//普通的提交方式,只提交一次,执行结束,线程不会退出。
  7. }
  8. }
  9. }

image.png
通过实验我们发现,本实验例程需要创建的线程数应小于等于线程池的线程容量,否则线程不会回收。具体表现在,当 for (int i = 0; i < 5; i++) 修改为 for (int i = 0; i <10 i++) 以后,仍然只有前5个线程执行,因为线程循环执行,会一直占用线程池的资源。
为了验证这一猜想我们将程序修改如下

  1. import java.util.Date;
  2. public class ScheduledThread extends Thread {
  3. private int i;
  4. public ScheduledThread(int in) {
  5. this.i = in;
  6. }
  7. @Override
  8. public void run() {
  9. Date date = new Date();
  10. System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
  11. }
  12. }
  13. //主程序如下:
  14. import java.util.concurrent.ScheduledThreadPoolExecutor;
  15. import java.util.concurrent.TimeUnit;
  16. public class ScheduledThreadTest {
  17. public static void main(String[] args) {
  18. ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(4);//设置线程个数
  19. for (int i = 0; i < 5; i++) {
  20. atpe.execute(new ScheduledThread(i));
  21. }
  22. }
  23. }

得到如下结果:
image.png
程序正常结束,且线程3被重复利用,并没达到线程池的最大容量4。
我们可以这样认为,newScheduledThreadPool这线程池可以使只执行一遍的线程以一定速率循环执行,但是如果以execute方式提交线程则不会重复执行。
我们对程序作出如下修改,使线程只执行一次:

  1. import java.util.Date;
  2. public class ScheduledThread extends Thread {
  3. private int i;
  4. public ScheduledThread(int in) {
  5. this.i = in;
  6. }
  7. @Override
  8. public void run() {
  9. Date date = new Date();
  10. System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
  11. }
  12. }

同时主程序修改为:

  1. import java.util.concurrent.ScheduledThreadPoolExecutor;
  2. import java.util.concurrent.TimeUnit;
  3. public class ScheduledThreadTest {
  4. public static void main(String[] args) {
  5. ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(4);//设置线程个数
  6. for (int i = 0; i < 5; i++) {
  7. //参数1:initialDelay表示首次执行任务的延迟时间,参数2:period表示每次执行任务的间隔时间,参数3:TimeUnit.MILLISECONDS执行的时间间隔数值单位
  8. atpe.scheduleAtFixedRate(new ScheduledThread(i),1000,2000,TimeUnit.MILLISECONDS);//以固定频率重复执行线程
  9. }
  10. }
  11. }

可以得到类似的结果:
image.png
我们可以发现线程2实现了重复利用,虽然创建的线程是一次执行,但却实现了重复执行的效果,这就是该线程池最大的特点。