美文网首页
并发艺术,先用后悟

并发艺术,先用后悟

作者: 晏子小七 | 来源:发表于2020-10-20 15:55 被阅读0次

    随着工作时间越来越久,慢慢的接触到的并发场景也越来越多,不得不说,这是每个Java开发者进阶之路的必经阶段,那我们就从简单的会用开始这段并发的学习之路吧。

    并发第一步,创建一个线程

    通常,启动一个程序,就相当于起了一个进程。每个电脑都会运行很多程序,所以你会在进程管理器中看到很多进程,一个进程里,可能会有很多线程在运行,也可能只有一个。

    main函数其实就是一个主线程。我们可以在这个主线程当中创建很多其他的线程。来看下面这段代码

    public class Test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        System.out.println("我叫" + Thread.currentThread().getName() + ",我喜欢吃烧烤");
                    }
                });
                t.start();
            }
        }
    }
    
    

    创建线程最常用的方式就是声明一个实现了Runnable接口的匿名内部类;然后将它作为创建Thread对象的参数;再然后调用Thread对象的start()方法进行启动。运行的结果如下。

    我叫Thread-1,我喜欢吃烧烤
    我叫Thread-3,我喜欢吃烧烤
    我叫Thread-2,我喜欢吃烧烤
    我叫Thread-0,我喜欢吃烧烤
    。。。
    
    

    从运行结果可以看出,线程的执行顺序不是从0-3按照顺序来执行的,而是有一定的随机性,原因是java并发是抢占机制。

    线程池初体验

    java本身提供了java.util.concurrent.Executors类一系列工厂方法用于创建线程池:

    public class Wanger {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();//创建一个缓存线程池
    
            for (int i = 0; i < 10; i++) {
                Runnable r = new Runnable() {
    
                    @Override
                    public void run() {
                        System.out.println("我叫" + Thread.currentThread().getName() + ",我喜欢吃烧烤");
                    }
                };
                executorService.execute(r);
            }
            executorService.shutdown();
        }
    }
    
    

    运行结果:

    我叫pool-1-thread-2,我喜欢吃烧烤
    我叫pool-1-thread-4,我喜欢吃烧烤
    我叫pool-1-thread-5,我喜欢吃烧烤
    我叫pool-1-thread-3,我喜欢吃烧烤
    我叫pool-1-thread-4,我喜欢吃烧烤
    我叫pool-1-thread-1,我喜欢吃烧烤
    我叫pool-1-thread-7,我喜欢吃烧烤
    我叫pool-1-thread-6,我喜欢吃烧烤
    我叫pool-1-thread-5,我喜欢吃烧烤
    我叫pool-1-thread-6,我喜欢吃烧烤
    
    

    调用该线程池的execute()方法可以重用以前的线程,只要该线程可用,除了newCacheedThreadPool还有其他三种线程池的创建:

    • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
    • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    但是我们经常看到有些地方说不允许Executors来直接创建线程池,可能会出现OOM的情况,为了避免这种情况的发生,我们该怎么创建线程池呢? 直接调用ThreadPoolExecutor的构造函数来创建线程池呗。其实Executors就是这么做的,只不过没有对BlockQueue指定容量。我们需要做的就是在创建的时候指定容量。代码示例如下。

    ExecutorService executor = new ThreadPoolExecutor(10, 10,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue(10));
    
    

    如何解决并发时共享资源竞争问题

    在Java中,解决共享资源竞争问题的首个解决方案就是使用关键字synchronized。当线程执行被synchronized保护的代码片段的时候,会对这段代码进行上锁,其他调用这段代码的线程会被阻塞,直到锁被释放。

    下面这段代码使用ThreadPoolExecutor创建了一个线程池,池里面的每个线程会对共享资源count进行+1操作。现在,闭上眼想一想,当1000个线程执行结束后,count的值会是多少呢?

    public class Wanger {
        public static int count = 0;
    
        public static int getCount() {
            return count;
        }
    
        public static void addCount() {
             count++;
        }
    
        public static void main(String[] args) {
            ExecutorService executorService = new ThreadPoolExecutor(10, 1000,
                    60L, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<Runnable>(10));
    
            for (int i = 0; i < 1000; i++) {
                Runnable r = new Runnable() {
    
                    @Override
                    public void run() {
                        Wanger.addCount();
                    }
                };
                executorService.execute(r);
            }
            executorService.shutdown();
            System.out.println(Wanger.count);
        }
    }
    
    

    运行结果的count值可能是995,996,998,但是很少会是1000,why? 这就是共享变量被占用的情况,在addCount()方法上加上synchronized关键字,线程就会进行同步操作,一个线程执行完才会释放锁给下一个等待的线程执行,这样就不会有共享资源的情况发生。 还有一种方法就是用锁的机制也可以完成资源的锁定,但是要注意锁的释放问题。

    相关文章

      网友评论

          本文标题:并发艺术,先用后悟

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