Java:并发不易,先学会用(1)

简介: Java:并发不易,先学会用

我从事Java编程已经11年了,绝对是个老兵;但对于Java并发编程,我只能算是个新兵蛋子。我说这话估计要遭到某些高手的冷嘲热讽,但我并不感到害怕。


因为我知道,每年都会有很多很多的新人要加入Java编程的大军,他们对“并发”编程中遇到的问题也会有感到无助的时候。而我,非常乐意与他们一道,对使用Java线程进行并发程序开发的基础知识进行新一轮的学习。


01、我们为什么要学习并发?


我的脑袋没有被如来佛祖开过光,所以喜欢一件事接着一件事的想,做不到“一脑两用”。但有些大佬就不一样,比如说诸葛亮,就能够一边想着琴谱一边谈着弹着琴,还能夹带着盘算出司马懿退兵后的打算。


诸葛大佬就有着超强的“并发”能力啊。换做是我,面对司马懿的千万大军,不仅弹不了琴,弄不好还被吓得屁滚尿流。


每个人都只有一个脑子,就像电脑只有一个CPU一样。但一个脑子并不意味着不能“一脑两用”,关键就在于脑子有没有“并发”的能力。


脑子要是有了并发能力,那真的是厉害到飞起啊,想想司马懿被气定神闲的诸葛大佬吓跑的样子就知道了。


对于程序来说,如果具有并发的能力,效率就能够大幅度地提升。你一定注册过不少网站,收到过不少验证码,如果网站的服务器端在发送验证码的时候,没有专门起一个线程来处理(并发),假如网络不好发生阻塞的话,那服务器端岂不是要从天亮等到天黑才知道你有没有收到验证码?如果就你一个用户也就算了,但假如有一百个用户呢?这一百个用户难道也要在那傻傻地等着,那真要等到花都谢了。


可想而知,并发编程是多么的重要!况且,懂不懂Java虚拟机和会不会并发编程,几乎是判定一个Java开发人员是不是高手的不三法则。所以要想挣得多,还得会并发啊!


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


通常,启动一个程序,就相当于起了一个进程。每个电脑都会运行很多程序,所以你会在进程管理器中看到很多进程。你会说,这不废话吗?


不不不,在我刚学习编程的很长一段时间内,我都想当然地以为这些进程就是线程;但后来我知道不是那么回事儿。一个进程里,可能会有很多线程在运行,也可能只有一个。


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


public class Wanger {
  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,我超喜欢沉默王二的写作风格

我叫Thread-5,我超喜欢沉默王二的写作风格

我叫Thread-4,我超喜欢沉默王二的写作风格

我叫Thread-6,我超喜欢沉默王二的写作风格

我叫Thread-7,我超喜欢沉默王二的写作风格

我叫Thread-8,我超喜欢沉默王二的写作风格

我叫Thread-9,我超喜欢沉默王二的写作风格


从运行的结果中可以看得出来,线程的执行顺序不是从0到9的,而是有一定的随机性。这是因为Java的并发是抢占式的,线程0虽然创建得最早,但它的“争宠”能力却一般,上位得比较艰辛。


03、并发第二步,创建线程池


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,我超喜欢沉默王二的写作风格


Executors的newCachedThreadPool()方法用于创建一个可缓存的线程池,调用该线程池的方法execute()可以重用以前的线程,只要该线程可用;比如说,pool-1-thread-4、pool-1-thread-5和pool-1-thread-6就得到了重用的机会。我能想到的最佳形象代言人就是女皇武则天。


如果没有可用的线程,就会创建一个新线程并添加到池中。当然了,那些60秒内还没有被使用的线程也会从缓存中移除。


另外,Executors的newFiexedThreadPool(int num)方法用于创建固定数目线程的线程池;newSingleThreadExecutor()方法用于创建单线程化的线程池(你能想到它应该使用的场合吗?)。


但是,故事要转折了。阿里巴巴的Java开发手册(可在「沉默王二」公众号的后台回复关键字「Java」获取)中明确地指出,不允许使用Executors来创建线程池。


不能使用Executors创建线程池,那么该怎么创建线程池呢?


直接调用ThreadPoolExecutor的构造函数来创建线程池呗。其实Executors就是这么做的,只不过没有对BlockQueue指定容量。我们需要做的就是在创建的时候指定容量。代码示例如下。


ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));
相关文章
|
16天前
|
安全 Java Go
Java vs. Go:并发之争
【4月更文挑战第20天】
22 1
|
16天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
3天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
11 0
|
4天前
|
Java
Java并发Futures和Callables类
Java程序`TestThread`演示了如何在多线程环境中使用`Futures`和`Callables`。它创建了一个单线程`ExecutorService`,然后提交两个`FactorialService`任务,分别计算10和20的阶乘。每个任务返回一个`Future`对象,通过`get`方法获取结果,该方法会阻塞直到计算完成。计算过程中模拟延迟以展示异步执行。最终,打印出10!和20!的结果。
|
5天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。
|
11天前
|
存储 安全 Java
【亮剑】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能
【4月更文挑战第30天】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能。数据被分割成多个Segment,每个拥有独立锁,允许多线程并发访问不同Segment。当写操作发生时,计算键的哈希值定位Segment并获取其锁;读操作通常无需锁定。内部会根据负载动态调整Segment,减少锁竞争。虽然使用不公平锁,但Java 8及以上版本提供了公平锁选项。理解其工作原理对开发高性能并发应用至关重要。
|
11天前
|
存储 Java 索引
【亮剑】Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap
【4月更文挑战第30天】本文介绍了Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap。文章展示了创建、添加、获取、删除和遍历元素的基本用法。ConcurrentHashMap的内部实现基于分段锁,每个段是一个独立的Hash表,通过分段锁实现并发控制。每个段内部采用数组+链表/红黑树的数据结构,当冲突过多时转为红黑树优化查询。此外,它有扩容机制,当元素超过阈值时,会逐段扩容并翻倍Segment数量,以保持高性能的并发访问。
|
17天前
|
Java API
[Java 并发基础]多线程编程
[Java 并发基础]多线程编程
|
17天前
|
安全 Java 调度
[Java并发基础] 共享内存
[Java并发基础] 共享内存
|
17天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程