总结:多线程集合类java.util.concurrent.*

2023-10-29

目录

一、java.util.concurrent - Java并发工具包

二、 阻塞队列BlockingQueue

2.1 BlockingQueue用法

2.2 BlockingQueue的方法

2.3 BlockingQueue的实现

2.4 Java中使用BlockingQueue的例子

三、数组阻塞队列ArrayBlockingQueue

四、延迟队列DelayQueue

五、链阻塞队列LinkedBlockingQueue

六、具有优先级的阻塞队列PriorityBlockingQueue

七、同步队列SynchronousQueue

八、BlockingDeque的使用

BlockingDeque的方法

BlockingDeque 继承自BlockingQueue

BlockingDeque 的实现

BlockingDeque 代码示例

九、链阻塞双端队列 LinkedBlockingDeque

十、并发 Map(映射) ConcurrentMap

java.util.concurrent.ConcurrentMap

ConcurrentMap的实现

ConcurrentHashMap

ConcurrentMap 例子

十一、并发导航映射 ConcurrentNavigableMap

headMap()

tailMap()

subMap()

更多方法

十二、闭锁 CountDownLatch

十三、栅栏 CyclicBarrier

十四、交换机 Exchanger

十五、信号量 Semaphore

十六、执行器服务 ExecutorService

1、介绍

2、ExecutorService 例子

3、方法

4、ExecutorService 实现

5、方法说明

十七、线程池执行者 ThreadPoolExecutor

十九、ScheduledExecutorService

1、介绍

3、ScheduledExecutorService 使用

二十、Lock

二十一、读写锁 ReadWriteLock

二十二、原子性布尔 AtomicBoolean

二十三、原子性整型 AtomicInteger

二十四、原子性长整型 AtomicLong

二十五、原子性引用型 AtomicReference


 

一、java.util.concurrent - Java并发工具包

Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包。这个包包含有一系列能够让 Java 的并发编程变得更加简单轻松的类。在这个包被添加以前,你需要自己去动手实现自己的相关工具类。

二、 阻塞队列BlockingQueue

java.util.concurrent 包里的 BlockingQueue 接口表示一个线程安全放入和提取实例的队列。本小节我将给你演示如何使用这个 BlockingQueue。

2.1 BlockingQueue用法

BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。如下图:

blocking-queue

一个线程往里边放,另外一个线程从里边取的一个 BlockingQueue。

一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。

如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。

负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会发生阻塞,直到一个生产线程把一个对象丢进队列。

2.2 BlockingQueue的方法

BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:

操作 抛异常 特定值 阻塞 超时
插入 add(o) offer(o) put(o) offer(o, timeout, timeunit)
移除 remove(o) poll(o) take(o) poll(timeout, timeunit)
检查 element(o) peek(o) 不可用 不可用

四组不同的行为方式解释:

  • 抛异常:如果试图的操作无法立即执行,抛一个异常。
  • 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
  • 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
  • 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

无法向一个 BlockingQueue 中插入 null。如果你试图插入 null,BlockingQueue 将会抛出一个 NullPointerException。
可以访问到 BlockingQueue 中的所有元素,而不仅仅是开始和结束的元素。比如说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。那么你可以调用诸如 remove(o) 方法来将队列之中的特定对象进行移除。但是这么干效率并不高(译者注:基于队列的数据结构,获取除开始或结束位置的其他对象的效率不会太高),因此你尽量不要用这一类的方法,除非你确实不得不那么做。

2.3 BlockingQueue的实现

BlockingQueue 是个接口,你需要使用它的实现之一来使用 BlockingQueue。java.util.concurrent 具有以下 BlockingQueue 接口的实现(Java 6):

  • ArrayBlockingQueue
  • DelayQueue
  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • SynchronousQueue

2.4 Java中使用BlockingQueue的例子

这里是一个 Java 中使用 BlockingQueue 的示例。本示例使用的是 BlockingQueue 接口的 ArrayBlockingQueue 实现。

首先,BlockingQueueExample 类分别在两个独立的线程中启动了一个 Producer 和 一个 Consumer。Producer 向一个共享的 BlockingQueue 中注入字符串,而 Consumer 则会从中把它们拿出来。

public class BlockingQueueExample {  

    public static void main(String[] args) throws Exception {  

        BlockingQueue queue = new ArrayBlockingQueue(1024);  

        Producer producer = new Producer(queue);  
        Consumer consumer = new Consumer(queue);  

        new Thread(producer).start();  
        new Thread(consumer).start();  

        Thread.sleep(4000);  
    }  
}  

以下是 Producer 类。注意它在每次 put() 调用时是如何休眠一秒钟的。这将导致 Consumer 在等待队列中对象的时候发生阻塞。

public class Producer implements Runnable{  

    protected BlockingQueue queue = null;  

    public Producer(BlockingQueue queue) {  
        this.queue = queue;  
    }  

    public void run() {  
        try {  
            queue.put("1");  
            Thread.sleep(1000);  
            queue.put("2");  
            Thread.sleep(1000);  
            queue.put("3");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  

以下是 Consumer 类。它只是把对象从队列中抽取出来,然后将它们打印到 System.out

public class Consumer implements Runnable{  

    protected BlockingQueue queue = null;  

    public Consumer(BlockingQueue queue) {  
        this.queue = queue;  
    }  

    public void run() {  
        try {  
            System.out.println(queue.take());  
            System.out.println(queue.take());  
            System.out.println(queue.take());  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

三、数组阻塞队列ArrayBlockingQueue

ArrayBlockingQueue 类实现了 BlockingQueue 接口。

ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(译者注:因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。

ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。

以下是在使用 ArrayBlockingQueue 的时候对其初始化的一个示例:

BlockingQueue queue = new ArrayBlockingQueue(1024);  
queue.put("1");  
Object object = queue.take();  

以下是使用了 Java 泛型的一个 BlockingQueue 示例。注意其中是如何对 String 元素放入和提取的:

BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1024);  
queue.put("1");  
String string = queue.take();  

四、延迟队列DelayQueue

总结:延时任务队列的原理与实现_小魏的博客的博客-CSDN博客_延时任务队列

DelayQueue 实现了 BlockingQueue 接口。DelayQueue 对元素进行持有直到一个特定的延迟到期。

注入其中的元素必须实现java.util.concurrent.Delayed接口,该接口定义:

public interface Delayed extends Comparable<Delayed> {  
    public long getDelay(TimeUnit timeUnit);  
}  

DelayQueue 将会在每个元素的 getDelay() 方法返回的值的时间段之后才释放掉该元素。如果返回的是 0 或者负值,延迟将被认为过期,该元素将会在 DelayQueue 的下一次 take 被调用的时候被释放掉。

传递给 getDelay 方法的 getDelay 实例是一个枚举类型,它表明了将要延迟的时间段。TimeUnit 枚举将会取以下值:

  • DAYS
  • HOURS
  • MINUTES
  • SECONDS
  • MILLISECONDS
  • MICROSECONDS
  • NANOSECONDS

正如你所看到的,Delayed 接口也继承了 java.lang.Comparable 接口,这也就意味着 Delayed 对象之间可以进行对比。这个可能在对 DelayQueue 队列中的元素进行排序时有用,因此它们可以根据过期时间进行有序释放。

以下是使用 DelayQueue 的例子:

public class DelayQueueExample {  

    public static void main(String[] args) {  
        DelayQueue queue = new DelayQueue();  
        Delayed element1 = new DelayedElement();  
        queue.put(element1);  
        Delayed element2 = queue.take();  
    }  
}  

DelayedElement 是我所创建的一个 DelayedElement 接口的实现类,它不在 Java.util.concurrent 包里。你需要自行创建你自己的 Delayed 接口的实现以使用 DelayQueue 类。

五、链阻塞队列LinkedBlockingQueue

LinkedBlockingQueue 类实现了 BlockingQueue 接口。

LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。

LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。

以下是 LinkedBlockingQueue 的初始化和使用示例代码:

BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();  
BlockingQueue<String> bounded   = new LinkedBlockingQueue<String>(1024);  
bounded.put("Value");  
String value = bounded.take();  

六、具有优先级的阻塞队列PriorityBlockingQueue

PriorityBlockingQueue 类实现了 BlockingQueue 接口。

PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 一样的排序规则。你无法向这个队列中插入 null 值。

所有插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。

注意 PriorityBlockingQueue 对于具有相等优先级(compare() == 0)的元素并不强制任何特定行为。
同时注意,如果你从一个 PriorityBlockingQueue 获得一个 Iterator 的话,该 Iterator 并不能保证它对元素的遍历是以优先级为序的。

以下是使用 PriorityBlockingQueue 的示例:

BlockingQueue queue   = new PriorityBlockingQueue();  
//String implements java.lang.Comparable  
queue.put("Value");  
String value = queue.take();  

七、同步队列SynchronousQueue

SynchronousQueue 类实现了 BlockingQueue 接口。

SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。

据此,把这个类称作一个队列显然是夸大其词了。它更多像是一个汇合点。

八、BlockingDeque的使用

在线程既是一个队列的生产者又是这个队列的消费者的时候可以使用到 BlockingDeque。如果生产者线程需要在队列的两端都可以插入数据,消费者线程需要在队列的两端都可以移除数据,这个时候也可以使用 BlockingDeque。BlockingDeque 图解:

blocking-deque

一个 BlockingDeque - 线程在双端队列的两端都可以插入和提取元素。

一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,插入线程将被阻塞,直到一个移除线程从该队列中移出了一个元素。如果双端队列为空,移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。

BlockingDeque的方法

BlockingDeque 具有 4 组不同的方法用于插入、移除以及对双端队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:

操作 抛异常 特定值 阻塞 超时
插入 addFirst(o) offerFirst(o) putFirst(o) offerFirst(o, timeout, timeunit)
移除 removeFirst(o) pollFirst(o) takeFirst(o) pollFirst(timeout, timeunit)
检查 getFirst(o) peekFirst(o)
操作 抛异常 特定值 阻塞 超时
插入 addLast(o) offerLast(o) putLast(o) offerLast(o, timeout, timeunit)
移除 removeLast(o) pollLast(o) takeLast(o) pollLast(timeout, timeunit)
检查 getLast(o) peekLast(o)

四组不同的行为方式解释:

  • 抛异常:如果试图的操作无法立即执行,抛一个异常。
  • 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
  • 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
  • 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

BlockingDeque 继承自BlockingQueue

BlockingDeque 接口继承自 BlockingQueue 接口。这就意味着你可以像使用一个 BlockingQueue 那样使用BlockingDeque。如果你这么干的话,各种插入方法将会把新元素添加到双端队列的尾端,而移除方法将会把双端队列的首端的元素移除。正如 BlockingQueue 接口的插入和移除方法一样。

以下是 BlockingDeque 对 BlockingQueue 接口的方法的具体内部实现:

BlockingQueue BlockingDeque
add() addLast()
offer() offerLast()
put() putLast()
offer(e, time, unit) offerLast(e, time, unit)
remove() removeFirst()
poll() pollFirst()
take() takeFirst()
poll(time, unit) pollLast(time, unit)
element() getFirst()
peek() peekFirst()

BlockingDeque 的实现

既然 BlockingDeque 是一个接口,那么你想要使用它的话就得使用它的众多的实现类的其中一个。java.util.concurrent 包提供了以下 BlockingDeque 接口的实现类:LinkedBlockingDeque

BlockingDeque 代码示例

以下是如何使用 BlockingDeque 方法的一个简短代码示例:

BlockingDeque<String> deque = new LinkedBlockingDeque<String>();  

deque.addFirst("1");  
deque.addLast("2");  

String two = deque.takeLast();  
String one = deque.takeFirst();  

九、链阻塞双端队列 LinkedBlockingDeque

LinkedBlockingDeque 类实现了 BlockingDeque 接口。

deque(双端队列) 是 “Double Ended Queue” 的缩写。因此,双端队列是一个你可以从任意一端插入或者抽取元素的队列。

LinkedBlockingDeque 是一个双端队列,在它为空的时候,一个试图从中抽取数据的线程将会阻塞,无论该线程是试图从哪一端抽取数据。

以下是 LinkedBlockingDeque 实例化以及使用的示例:

BlockingDeque<String> deque = new LinkedBlockingDeque<String>();  

deque.addFirst("1");  
deque.addLast("2");  

String two = deque.takeLast();  
String one = deque.takeFirst();  

十、并发 Map(映射) ConcurrentMap

java.util.concurrent.ConcurrentMap

java.util.concurrent.ConcurrentMap 接口表示了一个能够对别人的访问(插入和提取)进行并发处理的 java.util.Map。
ConcurrentMap 除了从其父接口 java.util.Map 继承来的方法之外还有一些额外的原子性方法。

ConcurrentMap的实现

既然 ConcurrentMap 是个接口,你想要使用它的话就得使用它的实现类之一。java.util.concurrent 包具备 ConcurrentMap 接口的以下实现类:ConcurrentHashMap

ConcurrentHashMap

ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。

另外一个不同点是,在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。

ConcurrentMap 例子

以下是如何使用 ConcurrentMap 接口的一个例子。本示例使用了 ConcurrentHashMap 实现类:

ConcurrentMap concurrentMap = new ConcurrentHashMap();  

concurrentMap.put("key", "value");  

Object value = concurrentMap.get("key");  

十一、并发导航映射 ConcurrentNavigableMap

java.util.concurrent.ConcurrentNavigableMap 是一个支持并发访问的 java.util.NavigableMap,它还能让它的子 map 具备并发访问的能力。所谓的 “子 map” 指的是诸如 headMap(),subMap(),tailMap() 之类的方法返回的 map。

NavigableMap 中的方法不再赘述,本小节我们来看一下 ConcurrentNavigableMap 添加的方法。

headMap()

headMap(T toKey) 方法返回一个包含了小于给定 toKey 的 key 的子 map。
如果你对原始 map 里的元素做了改动,这些改动将影响到子 map 中的元素(译者注:map 集合持有的其实只是对象的引用)。

以下示例演示了对 headMap() 方法的使用:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1", "one");  
map.put("2", "two");  
map.put("3", "three");  

ConcurrentNavigableMap headMap = map.headMap("2");  

headMap 将指向一个只含有键 “1” 的 ConcurrentNavigableMap,因为只有这一个键小于 “2”。关于这个方法及其重载版本具体是怎么工作的细节请参考 Java 文档。

tailMap()

tailMap(T fromKey) 方法返回一个包含了不小于给定 fromKey 的 key 的子 map。
如果你对原始 map 里的元素做了改动,这些改动将影响到子 map 中的元素(译者注:map 集合持有的其实只是对象的引用)。

以下示例演示了对 tailMap() 方法的使用:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1", "one");  
map.put("2", "two");  
map.put("3", "three");  

ConcurrentNavigableMap tailMap = map.tailMap("2");  

tailMap 将拥有键 “2” 和 “3”,因为它们不小于给定键 “2”。关于这个方法及其重载版本具体是怎么工作的细节请参考 Java 文档。

subMap()

subMap() 方法返回原始 map 中,键介于 from(包含) 和 to (不包含) 之间的子 map。示例如下:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1", "one");  
map.put("2", "two");  
map.put("3", "three");  

ConcurrentNavigableMap subMap = map.subMap("2", "3");  

返回的 submap 只包含键 “2”,因为只有它满足不小于 “2”,比 “3” 小。

更多方法

ConcurrentNavigableMap 接口还有其他一些方法可供使用,比如:

  • descendingKeySet()
  • descendingMap()
  • navigableKeySet()

关于这些方法更多信息参考官方 Java 文档。

十二、闭锁 CountDownLatch

总结:Java并发编程之CountDownLatch与CyclicBarrier_小魏的博客的博客-CSDN博客

java.util.concurrent.CountDownLatch 是一个并发构造,它允许一个或多个线程等待一系列指定操作的完成。
CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。

以下是一个简单示例。Decrementer 三次调用 countDown() 之后,等待中的 Waiter 才会从 await() 调用中释放出来。

public class Test {
    public static void main(String[] args) throws Exception{
        // 定义CountDownLatch  , 计数器数量为“子任务的个数”
        final CountDownLatch latch = new CountDownLatch(5);
        List<StringBuilder> stringBuilders = new ArrayList<>();
        stringBuilders.add(new StringBuilder("111"));
        stringBuilders.add(new StringBuilder("222"));
        stringBuilders.add(new StringBuilder("333"));
        stringBuilders.add(new StringBuilder("444"));
        stringBuilders.add(new StringBuilder("555"));
        ThreadPoolManager threadPoolManager = ThreadPoolManager.newInstance();
        for (StringBuilder stringBuilder : stringBuilders) {
            threadPoolManager.addExecuteTask(() -> {
                try {
                    handle(stringBuilder, latch);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        Console.log("{}:等待任务执行完毕", new Date());
        latch.await();
        Console.log("{}:全部任务已经执行完毕", new Date());

        Console.log("stringBuilders:{}", JSONObject.toJSONString(stringBuilders));
    }

    public static void handle(StringBuilder stringBuilder, CountDownLatch latch) throws InterruptedException {
        String name = Thread.currentThread().getName();
        stringBuilder.append("#666");
        Thread.sleep(3000);
        Console.log("线程:{} 执行完毕!", name);
        latch.countDown();
    }
}

十三、栅栏 CyclicBarrier

java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步。换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情

十四、交换机 Exchanger

java.util.concurrent.Exchanger 类表示一种两个线程可以进行互相交换对象的会和点。这种机制图示如下:

exchanger

两个线程通过一个 Exchanger 交换对象。

交换对象的动作由 Exchanger 的两个 exchange() 方法的其中一个完成。以下是一个示例:

class Person{
	String name;
	String age;
	
	public Person(String name, String age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAge() {
		return age;
	}
	public void setAge(String age) {
		this.age = age;
	}
	
}
 
class ExchangerRunnable implements Runnable{  

    Exchanger<Person> exchanger = null;  
    Person object    = null;  

    public ExchangerRunnable(Exchanger<Person> exchanger, Person object) {  
        this.exchanger = exchanger;  
        this.object = object;  
    }  

    public void run() {  
        try {  
        	Person previous = this.object;  

            this.object = this.exchanger.exchange(this.object);  

            System.out.println(  
                    Thread.currentThread().getName() +  
                    " 的老师由 " + previous.getName() + " 变为 " + this.object.getName()  
            );  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
 
public class ExchangerTest {
	public static void main(String[] args) {
 
		Exchanger<Person> exchanger = new Exchanger<Person>();
		Person teacherZhang = new Person("张老师", "50");
		Person teacherLiu = new Person("刘老师", "37");

		//起初老师为张老师
		ExchangerRunnable exchangerRunnable1 =  new ExchangerRunnable(exchanger, teacherZhang);  
		//起初老师为刘老师
		ExchangerRunnable exchangerRunnable2 =  new ExchangerRunnable(exchanger, teacherLiu);  

		new Thread(exchangerRunnable1, "weiwei").start();  
		new Thread(exchangerRunnable2, "zhangyu").start(); 
 
	}
}

以上程序输出:

weiwei 的老师由 张老师 变为 刘老师
zhangyu 的老师由 刘老师 变为 张老师

十五、信号量 Semaphore

java.util.concurrent.Semaphore 类是一个计数信号量。

Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

案例:

假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才

能继续使用。那么我们就可以通过Semaphore来实现:


public class SemaphoreTest {
	public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
}

class Worker extends Thread{
    private int num;
    private Semaphore semaphore;
    public Worker(int num,Semaphore semaphore){
        this.num = num;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println("工人"+this.num+"占用一个机器在生产...");
            Thread.sleep(2000);
            System.out.println("工人"+this.num+"释放出机器");
            semaphore.release();           
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出:

工人0占用一个机器在生产...
工人2占用一个机器在生产...
工人1占用一个机器在生产...
工人3占用一个机器在生产...
工人4占用一个机器在生产...
工人4释放出机器
工人3释放出机器
工人5占用一个机器在生产...
工人6占用一个机器在生产...
工人0释放出机器
工人2释放出机器
工人7占用一个机器在生产...
工人1释放出机器
工人5释放出机器
工人6释放出机器
工人7释放出机器

十六、执行器服务 ExecutorService

1、介绍

java.util.concurrent.ExecutorService是Java中对线程池定义的一个接口。

2、ExecutorService 例子

以下是一个简单的 ExecutorService 例子:

ExecutorService executorService = Executors.newFixedThreadPool(10);  

executorService.execute(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  

executorService.shutdown();  

 其它例子:

ExecutorService executorService1 = Executors.newSingleThreadExecutor();  
ExecutorService executorService2 = Executors.newFixedThreadPool(10);  
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);  

ExecutorService 使用

3、方法

4、ExecutorService 实现

既然 ExecutorService 是个接口,如果你想用它的话就得去使用它的实现类之一。java.util.concurrent 包提供了 ExecutorService 接口的以下实现类:

  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor

5、方法说明

接下来我们挨个看一下这些方法。

execute(Runnable)

execute(Runnable) 方法要求一个Runnable对象,然后对它进行异步执行。以下是使用 ExecutorService 执行一个 Runnable 的示例:

注:execute方法是在定级接口Executor中定义的,ExecutorService继承了Executor。

ExecutorService executorService = Executors.newSingleThreadExecutor();  

executorService.execute(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  

executorService.shutdown();  

没有办法得知被执行的 Runnable 的执行结果。如果有需要的话你得使用一个 Callable(以下将做介绍)。

6、submit(Runnable)

submit(Runnable) 方法也要求一个 Runnable 实现类,但它返回一个 Future 对象。这个 Future 对象可以用来检查 Runnable 是否已经执行完毕。

以下是 ExecutorService submit() 示例:

Future future = executorService.submit(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  

future.get();  //returns null if the task has finished correctly.  

7、submit(Callable)

submit(Callable) 方法类似于 submit(Runnable) 方法,除了它所要求的参数类型之外。Callable 实例除了它的 call() 方法能够返回一个结果之外和一个 Runnable 很相像。Runnable.run() 不能够返回一个结果。
Callable 的结果可以通过 submit(Callable) 方法返回的 Future 对象进行获取。以下是一个 ExecutorService Callable 示例:

Future future = executorService.submit(new Callable(){  
    public Object call() throws Exception {  
        System.out.println("Asynchronous Callable");  
        return "Callable Result";  
    }  
});  

System.out.println("future.get() = " + future.get());  

以上代码输出:

Asynchronous Callable
future.get() = Callable Result

8、ExecutorService 关闭

如果是临时创建的ExecutorService,使用完 ExecutorService 之后应该将其关闭,释放资源。

使用 ExecutorService 类时,经常用到shutdown() 、shutdownNow()2个方法

  • shutdown():停止接收新任务,已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成,都执行结束才真正停止;
  • shutdownNow():跟 shutdown() 一样,先停止接收新submit的任务;队列中等待的移除;

9、invokeAny()

invokeAny(…)方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。

以下是示例代码:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 2";
}
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
    return "Task 3";
}
});

String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

10、invokeAll()

invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是invokeAll(…)执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。

十七、线程池执行者 ThreadPoolExecutor

ThreadPoolExecutor 是 ExecutorService 接口的一个实现。ThreadPoolExecutor 使用其内部池中的线程执行给定任务(Callable 或者 Runnable)。

ThreadPoolExecutor包含的线程池能够包含不同数量的线程。池中线程的数量由以下变量决定:

  • corePoolSize
  • maximumPoolSize

当一个任务委托给线程池时,如果池中线程数量低于 corePoolSize,一个新的线程将被创建,即使池中可能尚有空闲线程。如果内部任务队列已满,而且有至少 corePoolSize 正在运行,但是运行线程的数量低于 maximumPoolSize,一个新的线程将被创建去执行该任务。

创建一个 ThreadPoolExecutor

int  corePoolSize  =    5;  
int  maxPoolSize   =   10;  
long keepAliveTime = 5000;  

ExecutorService threadPoolExecutor =  
        new ThreadPoolExecutor(  
                corePoolSize,  
                maxPoolSize,  
                keepAliveTime,  
                TimeUnit.MILLISECONDS,  
                new LinkedBlockingQueue<Runnable>()  
                );  

十九、ScheduledExecutorService

1、介绍

java.util.concurrent.ScheduledExecutorService 是一个 ExecutorService。

它能够将任务延后执行,或者间隔固定时间多次执行。 任务由一个工作者线程异步执行,而不是由提交任务给 ScheduledExecutorService 的那个线程执行。

以下是一个简单的 ScheduledExecutorService 示例:

ScheduledExecutorService scheduledExecutorService =  
        Executors.newScheduledThreadPool(5);  

ScheduledFuture scheduledFuture =  
    scheduledExecutorService.schedule(new Callable() {  
        public Object call() throws Exception {  
            System.out.println("Executed!");  
            return "Called!";  
        }  
    }, 5,  TimeUnit.SECONDS);  

首先一个内置 5 个线程的 ScheduledExecutorService 被创建。之后一个 Callable 接口的匿名类示例被创建然后传递给 schedule() 方法。后边的俩参数定义了 Callable 将在 5 秒钟之后被执行。

2、ScheduledExecutorService 实现

既然 ScheduledExecutorService 是一个接口,你要用它的话就得使用 java.util.concurrent 包里对它的某个实现类。ScheduledExecutorService 具有以下实现类:ScheduledThreadPoolExecutor

如何创建一个 ScheduledExecutorService 取决于你采用的它的实现类。但是你也可以使用 Executors 工厂类来创建一个 ScheduledExecutorService 实例。比如:

ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(5);  

3、ScheduledExecutorService 使用

一旦你创建了一个 ScheduledExecutorService,你可以通过调用它的以下方法:

  • schedule (Callable task, long delay, TimeUnit timeunit)
  • schedule (Runnable task, long delay, TimeUnit timeunit)
  • scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
  • scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

下面我们就简单看一下这些方法。

schedule (Callable task, long delay, TimeUnit timeunit)

这个方法计划指定的 Callable 在给定的延迟之后执行。
这个方法返回一个 ScheduledFuture,通过它你可以在它被执行之前对它进行取消,或者在它执行之后获取结果。
以下是一个示例:

ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(5);  

ScheduledFuture scheduledFuture =  
    scheduledExecutorService.schedule(new Callable() {  
        public Object call() throws Exception {  
            System.out.println("Executed!");  
            return "Called!";  
        }  
    },  5,  TimeUnit.SECONDS);  

System.out.println("result = " + scheduledFuture.get());  

scheduledExecutorService.shutdown();  

示例输出结果:

Executed!
result = Called!

4、ScheduledExecutorService 关闭

可以使用从 ExecutorService 接口继承来的 shutdown() 或 shutdownNow() 方法将 ScheduledExecutorService 关闭。参见 ExecutorService 关闭部分以获取更多信息。

二十、Lock

总结:Java中的锁_小魏的博客的博客-CSDN博客_java中的锁

java.util.concurrent.locks.Lock是一个类似于 synchronized 块的线程同步机制。但是 Lock 比 synchronized 块更加灵活、精细。

既然 Lock 是一个接口,在你的程序里需要使用它的实现类之一来使用它。以下是一个简单示例:

Lock lock = new ReentrantLock();  

lock.lock();  

//......  

lock.unlock();  

首先创建了一个 Lock 对象。之后调用了它的 lock() 方法。这时候这个 lock 实例就被锁住啦。任何其他再过来调用 lock() 方法的线程将会被阻塞住,直到锁定 lock 实例的线程调用了 unlock() 方法。最后 unlock() 被调用了,lock 对象解锁了,其他线程可以对它进行锁定了。

Lock 和 synchronized对比:

一个 Lock 对象和一个 synchronized 代码块之间的主要不同点是:

  • synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
  • synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

Lock 的方法

Lock 接口具有以下主要方法:

  • lock()

lock() 将 Lock 实例锁定。如果该 Lock 实例已被锁定,调用 lock() 方法的线程将会阻塞,直到 Lock 实例解锁。

  • lockInterruptibly()

lockInterruptibly() 方法将会被调用线程锁定,除非该线程被打断。此外,如果一个线程在通过这个方法来锁定 Lock 对象时进入阻塞等待,而它被打断了的话,该线程将会退出这个方法调用。

  • tryLock()

tryLock() 方法试图立即锁定 Lock 实例。如果锁定成功,它将返回 true,如果 Lock 实例已被锁定该方法返回 false。这一方法永不阻塞。

  • tryLock(long timeout, TimeUnit timeUnit)

tryLock(long timeout, TimeUnit timeUnit) 的工作类似于 tryLock() 方法,除了它在放弃锁定 Lock 之前等待一个给定的超时时间之外。

  • unlock()

unlock() 方法对 Lock 实例解锁。一个 Lock 实现将只允许锁定了该对象的线程来调用此方法。其他(没有锁定该 Lock 对象的线程)线程对 unlock() 方法的调用将会抛一个未检查异常(RuntimeException)。

二十一、读写锁 ReadWriteLock

ReentrantReadWriteLock实现了ReadWriteLock接口。

ReadWriteLock接口包含两个方法:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     */
    Lock readLock();


    /**
     * Returns the lock used for writing.
     */
    Lock writeLock();
}

ReentrantReadWriteLock 是读写锁,和ReentrantLock会有所不同,对于读多写少的场景使用ReentrantReadWriteLock 性能会比ReentrantLock高出不少。在多线程读时互不影响,不像ReentrantLock即使是多线程读也需要每个线程获取锁。不过任何一个线程在写的时候就和ReentrantLock类似,其他线程无论读还是写都必须获取锁。需要注意的是同一个线程可以拥有 writeLock 与 readLock (但必须先获取 writeLock 再获取 readLock, 反过来进行获取会导致死锁)

案例:

    private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Integer test = 2;
        ExecutorService threadPool = Executors.newFixedThreadPool(10);


        for (int i = 0; i < 3; i++) {
            threadPool.execute(() -> {
                try {
                    readWriteLock.writeLock().lockInterruptibly();
                    System.out.println("写入数据" + test);
                    // 假装耗时操作
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readWriteLock.writeLock().unlock();
                }
            });
        }
        for (int i = 0; i < 20; i++) {
            threadPool.execute(() -> {
                try {
                    readWriteLock.readLock().lockInterruptibly();
                    System.out.println("读取数据" + test);
                    // 假装耗时操作
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readWriteLock.readLock().unlock();
                }
            });
        }
    }

二十二、原子性布尔 AtomicBoolean

AtomicBoolean 类拥有一些先进的原子性操作,比如 compareAndSet()。

修改AtomicBoolean 的值

你可以通过 getAndSet() 方法来交换一个 AtomicBoolean 实例的值。getAndSet() 方法将返回 AtomicBoolean 当前的值,并将为 AtomicBoolean 设置一个新值。示例如下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  
boolean oldValue = atomicBoolean.getAndSet(false);  

以上代码执行后 oldValue 变量的值为 true,atomicBoolean 实例将持有 false 值。代码成功将 AtomicBoolean 当前值 ture 交换为 false。

比较并设置 AtomicBoolean 的值

compareAndSet() 方法允许你对 AtomicBoolean 的当前值与一个期望值进行比较,如果当前值等于期望值的话,将会对 AtomicBoolean 设定一个新值。compareAndSet() 方法是原子性的,因此在同一时间之内有单个线程执行它。因此 compareAndSet() 方法可被用于一些类似于锁的同步的简单实现。

以下是一个 compareAndSet() 示例:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

boolean expectedValue = true;  
boolean newValue      = false;  

boolean wasNewValueSet = atomicBoolean.compareAndSet(expectedValue, newValue);  

本示例对 AtomicBoolean 的当前值与 true 值进行比较,如果相等,将 AtomicBoolean 的值更新为 false。

二十三、原子性整型 AtomicInteger

AtomicInteger 类为我们提供了一个可以进行原子性读和写操作的 int 变量,它还包含一系列先进的原子性操作,比如 compareAndSet()。

AtomicInteger atomicInteger = new AtomicInteger(123);  
int theValue = atomicInteger.get();  

设置 AtomicInteger 的值

你可以通过 set() 方法对 AtomicInteger 的值进行重新设置。以下是 AtomicInteger.set() 示例:

AtomicInteger atomicInteger = new AtomicInteger(123);  
atomicInteger.set(234);  

以上示例创建了一个初始值为 123 的 AtomicInteger,而在第二行将其值更新为 234。

比较并设置 AtomicInteger 的值

AtomicInteger 类也通过了一个原子性的 compareAndSet() 方法。这一方法将 AtomicInteger 实例的当前值与期望值进行比较,如果二者相等,为 AtomicInteger 实例设置一个新值。AtomicInteger.compareAndSet() 代码示例:

AtomicInteger atomicInteger = new AtomicInteger(123);  

int expectedValue = 123;  
int newValue      = 234;  
atomicInteger.compareAndSet(expectedValue, newValue);  

本示例首先新建一个初始值为 123 的 AtomicInteger 实例。然后将 AtomicInteger 与期望值 123 进行比较,如果相等,将 AtomicInteger 的值更新为 234。

增加 AtomicInteger 值

AtomicInteger 类包含有一些方法,通过它们你可以增加 AtomicInteger 的值,并获取其值。这些方法如下:

  • addAndGet(int value):给 AtomicInteger 增加了一个值,然后返回增加后的值
  • getAndAdd(int value):为 AtomicInteger 增加了一个值,但返回的是增加以前的 AtomicInteger 的值
  • getAndIncrement():方法类似于 getAndAdd(),但每次只将 AtomicInteger 的值加 1。
  • incrementAndGet():方法类似于 addAndGet(),但每次只将 AtomicInteger 的值加 1。

减小 AtomicInteger 的值

AtomicInteger 类还提供了一些减小 AtomicInteger 的值的原子性方法。这些方法是:

  • decrementAndGet()
  • getAndDecrement()

decrementAndGet() 将 AtomicInteger 的值减一,并返回减一后的值。getAndDecrement() 也将 AtomicInteger 的值减一,但它返回的是减一之前的值。

二十四、原子性长整型 AtomicLong

AtomicLong 类为我们提供了一个可以进行原子性读和写操作的 long 变量,它还包含一系列先进的原子性操作,比如 compareAndSet()。

类似于AtomicInteger 类。

二十五、原子性引用型 AtomicReference

AtomicReference 提供了一个可以被原子性读和写的对象引用变量。原子性的意思是多个想要改变同一个 AtomicReference 的线程不会导致 AtomicReference 处于不一致的状态。AtomicReference 还有一个 compareAndSet() 方法,通过它你可以将当前引用于一个期望值(引用)进行比较,如果相等,在该 AtomicReference 对象内部设置一个新的引用。

创建一个 AtomicReference

创建 AtomicReference 如下:

AtomicReference atomicReference = new AtomicReference();  

如果你需要使用一个指定引用创建 AtomicReference,可以:

String initialReference = "the initially referenced string";  
AtomicReference atomicReference = new AtomicReference(initialReference);  

创建泛型 AtomicReference

你可以使用 Java 泛型来创建一个泛型 AtomicReference。示例:

AtomicReference<String> atomicStringReference = new AtomicReference<String>();  

你也可以为泛型 AtomicReference 设置一个初始值。示例:

String initialReference = "the initially referenced string";  
AtomicReference<String> atomicStringReference =  new AtomicReference<String>(initialReference);  

获取 AtomicReference 引用

你可以通过 AtomicReference 的 get() 方法来获取保存在 AtomicReference 里的引用。如果你的 AtomicReference 是非泛型的,get() 方法将返回一个 Object 类型的引用。如果是泛型化的,get() 将返回你创建 AtomicReference 时声明的那个类型。

先来看一个非泛型的 AtomicReference get() 示例:

String initialReference = "the initially referenced string";  
AtomicReference<String> atomicReference =   new AtomicReference<String>(initialReference);
String reference = atomicReference.get();  
System.out.println(reference);


//输出
//the initially referenced string

注意如何对 get() 方法返回的引用强制转换为 String。
泛型化的 AtomicReference 示例:

AtomicReference<String> atomicReference =   new AtomicReference<String>("first value referenced");
String reference = atomicReference.get();  

编译器知道了引用的类型,所以我们无需再对 get() 返回的引用进行强制转换了。

设置 AtomicReference 引用

你可以使用 get() 方法对 AtomicReference 里边保存的引用进行设置。如果你定义的是一个非泛型 AtomicReference,set() 将会以一个 Object 引用作为参数。如果是泛型化的 AtomicReference,set() 方法将只接受你定义给的类型。

AtomicReference set() 示例:

AtomicReference atomicReference = new AtomicReference();  
atomicReference.set("New object referenced");  

这个看起来非泛型和泛型化的没啥区别。真正的区别在于编译器将对你能够设置给一个泛型化的 AtomicReference 参数类型进行限制。

比较并设置 AtomicReference 引用

AtomicReference 类具备了一个很有用的方法:compareAndSet()。compareAndSet() 可以将保存在 AtomicReference 里的引用于一个期望引用进行比较,如果两个引用是一样的(并非 equals() 的相等,而是 == 的一样),将会给AtomicReference 实例设置一个新的引用。

如果 compareAndSet() 为 AtomicReference 设置了一个新的引用,compareAndSet() 将返回 true。否则compareAndSet() 返回 false。

AtomicReference compareAndSet() 示例:

String initialReference = "initial value referenced";  

AtomicReference<String> atomicStringReference = new AtomicReference<String>(initialReference);  

String newReference = "new value referenced";  
boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  

exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  

本示例创建了一个带有一个初始引用的泛型化的 AtomicReference。之后两次调用 comparesAndSet()来对存储值和期望值进行对比,如果二者一致,为 AtomicReference 设置一个新的引用。第一次比较,存储的引用(initialReference)和期望的引用(initialReference)一致,所以一个新的引用(newReference)被设置给 AtomicReference,compareAndSet() 方法返回 true。第二次比较时,存储的引用(newReference)和期望的引用(initialReference)不一致,因此新的引用没有被设置给 AtomicReference,compareAndSet() 方法返回 false。

参考:

Java并发编程-并发工具包java.util.concurrent使用指南

java并发包工具(java.util.Concurrent)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

总结:多线程集合类java.util.concurrent.* 的相关文章

  • 在 catch 块中重新抛出异常是否有意义?

    从 catch 块中抛出异常只是为了记录消息以便我们确定导致异常的原因是否有意义 Code public void saveLogs Logs logs throws RemoteException try LogsOps saveLogs
  • 如何使用 Java 以独立于平台的方式读取 Windows 共享驱动器?

    如何使用 Java 从 Windows 共享驱动器中读取数据 以便执行读取的 Java 代码可以在任何平台上同样正确地运行 您可以使用JCIFS http jcifs samba org 使用纯 Java 代码访问 SMB CIFS 共享
  • JDT - 尝试更改类型的超类。我不知道超级类的限定名称

    我有一个程序 除其他任务外 还必须使用 JDT 更改某些类的超类 我有两个字符串 其中包含要交换的超类的限定名称 例如 org example John 和 org example Smith 并且我正在解析整个 AST 搜索扩展这些类的类
  • JAVA_HOME环境变量和Java JDK趣事

    我想让 Java 在 1 6xxx 上运行 我更改了 JAVA HOME 变量并将其指向目录 C Program Files Java jdk1 6 0 16 我重新启动 PC 我想我可以检查我的机器指向哪个版本的 Java 但它仍然指向旧
  • 如何在Java中使用我的密码加密和解密字符串(PC而非移动平台)? [复制]

    这个问题在这里已经有答案了 我想加密一个字符串然后将其放入文件中 当我想要的时候也想解密它 我不需要很强的安全性 我只是想让其他人更难获取我的数据 我尝试了几种方法 这是这些 Md5加密 如何在 Android 中对字符串进行哈希处理 ht
  • 如何通过两跳 SSH 隧道使用 JProfiler

    我正在尝试将 JProfiler 连接到在我将调用的服务器上运行的 JVMremote 该服务器只能从我的工作站访问 local 通过我将调用的另一台服务器middle 我的计划是将 JProfiler 连接到remote是这样的 安装 J
  • LibGDX 闪烁

    我已经使用 LibGDX UI 设置来启动一个项目 我在实现 ApplicationListener 中唯一拥有的是 public void create setScreen new LoadingScreen this 这应该会触发 Lo
  • docker 中带有参数的 jar 文件

    Helo 我有一个 java jar 文件 当我从终端运行它时 它会接受一堆参数作为输入 我想制作一个 docker 映像并运行它 其中包含 jar 文件 我仍然可以在其中传递 jar 文件的参数 将 jar 文件设置为您的入口点 http
  • Logback 配置在单行上有异常吗?

    我的日志被提取 传输并合并到 elasticsearch 中 多行事件很难跟踪和诊断 有没有办法使用收集器和正则表达式将异常行分组到单个记录中登录配置 https logback qos ch manual layouts html xTh
  • 最终类中的静态函数是否隐式最终?

    我的问题基本上与this https stackoverflow com q 8766476 3882565一 但这是否也适用于static功能 我想了解 编译器是否处理所有static函数在一个final类为final 是否添加final
  • 检查对象是否为空

    我有一个链表 其中第一个节点包含空对象 表示firstNode data等于null firstNode nextPointer null firstNode previousPointer null 我想检查firstNode 是否为空
  • 当对话框打开时如何处理屏幕方向变化?

    我有一个 Android 应用程序 它已经在处理方向的更改 即有一个android configChanges orientation 在清单和onConfigurationChange 活动中的处理程序切换到适当的布局并准备它 我有一个横
  • oracle.jdbc.driver.OracleDriver ClassNotFoundException

    这是我收到错误的代码 我的classes12 jar已作为外部 jar 导入 import java io IOException import java io PrintWriter import java sql Connection
  • 如何保存/加载 BigInteger 数组

    我想保存 加载BigInteger数组传入 传出 SharedPreferences 如何做呢 例如对于以下数组 private BigInteger dataCreatedTimes new BigInteger 20 Using Gso
  • 在Java中,为什么某些变量首先需要初始化,而其他变量只需要声明?

    我试图更深入地理解我是否遗漏了一些关于 Java 何时需要变量初始化与简单声明的理解 在以下代码中 不需要为变量 row 赋值即可编译和运行 但变量 column 则需要赋值 注意 该程序没有任何用处 它已被修剪为仅显示此问题所需的内容 以
  • 如何在 VSCode 中热重载 Tomcat 服务器

    我正在从 Eclipse IDE VSCode 分别用于编码 Java servlet 和 HTML CSS JS 网页 迁移到仅使用 Visual Studio Code 因为它的轻量级 我为 VSCode 安装了几个 Java 扩展 R
  • 无法取消 GWT 中的重复计时器

    我正在尝试在 GWT 中安排一个重复计时器 它将每一毫秒运行一次 轮询某个事件 如果发现满意 则执行某些操作并取消计时器 我尝试这样做 final Timer t new Timer public void run if condition
  • 每次修改代码时都必须 mvn clean install

    我不是来自 Java 世界 但我必须为我的一个项目深入研究它 我不明白为什么每次修改或更新代码时 都必须 mvn clean install 来调试代码的最新版本 你知道为什么吗 尝试按Ctrl Shift F9 热插拔 有时会有所帮助
  • Encog:BasicNetwork:无需预先构建数据集的在线学习

    我正在尝试使用 encog 库作为强化学习问题的函数逼近器 更准确地说 我正在尝试启动并运行多层感知器 BasicNetwork 由于我的代理将根据我选择的任何 RL 算法以某种方式探索世界 因此我无法预先构建任何 BasicNeuralD
  • Java GridBagConstraints gridx 和 gridy 不工作?

    我正在尝试使用gridx and gridy定位我的按钮的约束 但它们不起作用 如果我改变gridx and gridy变量 什么也没有发生 如果我将填充更改为GridBagConstraints to NONE 仍然不行 我在这里错过了什

随机推荐

  • Blazor 路由及导航开发指南

    翻译自 Waqas Anwar 2021年4月2日的文章 A Developer s Guide To Blazor Routing and Navigation 1 检查传入的请求 URL 并将它们导航到对应的视图或页面是每个单页应用程序
  • keil5——安装教程附资源包

    目录 一 安装mdk 1 下载keil5的压缩包 解压后 点击运行mdk514 2 在弹出来的以下界面中 点击 Next 3 下一个界面中 勾选 I agree 然后再次点击 Next 4 到达下面这个界面之后 记住原始默认安装路径 5 点
  • 开机出现recovering journal解决办法

    系统环境 安装的双系统win10和Ubuntu 18 04 出现问题 电脑开机后登陆Ubuntu系统出现recovering journal clean xxx files xxx blocks 如下图所示 出现问题了 然后就开始找解决方法
  • 面试遇到了算法题?看这篇就够啦。

    原文地址 github com kdn251 interviews 译文出自 掘金翻译计划 译者 王下邀月熊 校对者 PhxNirvana 根号三 这个 链接 用来查看本翻译与英文版是否有差别 如果你没有看到 README md 发生变化
  • 模块高可用性部署概述

    高可用三种模式 主从热备 哨兵模式 集群模式 番外 总结了Redis的几种高可用方式 对于做后端模块的同学来说 可以根据 或是借鉴其思路做自己的模块可用性部署 主从热备 主从热备 是高可用性中最基础的解决方案 主要作用于主机异常宕机后 可以
  • 《JAVA与模式》之观察者模式

    在阎宏博士的 JAVA与模式 一书中开头是这样描述观察者 Observer 模式的 观察者模式是对象的行为模式 又叫发布 订阅 Publish Subscribe 模式 模型 视图 Model View 模式 源 监听器 Source Li
  • python manage.py makemigrations时出现No changes detected

    当输入迁移命令 python manage py makemigrations 时出现No changes detected 一 在项目的settings py文件 INSTALLED APPS 中插入 app名 如 message 是我的
  • XSS—存储型xss

    xss gt 跨站脚本攻击 gt 前端代码注入 gt 用户输入的数据会被当做前端代码执行 原理 使用者提交的XSS代码被存储到服务器上的数据库里或页面或某个上传文件里 导致用户访问页面展示的内容时直接触发xss代码 输入内容后直接在下方回显
  • 5G产业链正在蓬勃发展 边缘计算的未来前景如何?

    在5G商用化启动后 相关产业链的价值正在被大多数投资者挖掘 其中 边缘云的潜力尤为引人关注 可以说 目前 边缘云发已构建出独特的商业模式 其上建立的新生态正在被越来越多的企业接受 数科星球 原创 作者丨数数 编辑丨十里香 在过去 5G被认为
  • Spring Boot 学习研究笔记(十一)IDEA SpringBoot 打包jar 两种方式

    IDEA SpringBoot 打包jar 两种方式 一 配置JAR 相关准备工作 1 选中打包的项目 File gt Project Structure 2 选择需要打包的模块 3 配置入口 JAR 依赖库文件 META INF 目录 4
  • ZTree修改最前面的加减号、图标及大小、字体及大小、以及间距

    效果如下 最前面的加减号 图标及大小 字体及大小 以及间距都可以更改 具体如下 1 首先 把Ztree官方给的Demo中的如下红框中的样式文件复制到自己项目的css目录中 其中蓝色部分是我自己添加的自定义图标 2 新建Ztree html如
  • python中在不同文件夹/同一个文件夹两种情况下如何引用另一个.py文件

    原文链接 代码可到原文直接复制 https blog csdn net ispringmw article details 119255858
  • Redis集群数据分片机制说明

    Redis 集群简介 Redis Cluster 是 Redis 的分布式解决方案 在 3 0 版本正式推出 有效地解决了 Redis 分布式方面的需求 Redis Cluster 一般由多个节点组成 节点数量至少为 6 个才能保证组成完整
  • BeyondCompare去掉时间戳的匹配

    BeyondCompare是一个非常好用的对比工具 其中在对比文件的时候 我们可能需要在不进入文件的情况下 一眼就看出两个文件是否一样 在没有去掉时间戳的匹配的情况下 是看不出来的 因为可能文件的内容是一样的 而文件的创建修改时间不一样 这
  • FastSAM 论文解读

    论文名称 Fast Segment Anything 论文地址 http export arxiv org pdf 2306 12156 代码地址 GitHub CASIA IVA Lab FastSAM Fast Segment Anyt
  • 案例 - 图书管理+聊天机器人

    文章目录 图书管理 8 1 渲染UI结构 8 2 案例用到的库和插件 8 3 渲染图书列表 核心代码 8 4 删除图书 核心代码 8 5 添加图书 核心代码 聊天机器人 9 1 演示案例要完成的效果 9 2 梳理案例的代码结构 9 3 将用
  • C# 关于运算符重载--矢量之间的运算

    运算符重载就是指重写 1 1 中的加号 那样我们可以实现1 1 1 类似的 其他运算符重载也是这样的道理 然运算符的重载用来干这些事显得鸡肋了些 更多的是 通过运算符重载去实现一般的加减乘除不能实现的运算 例如 gt 矢量的加减乘除 cla
  • 数据分析及算法总结

    一 K 近邻算 工作原理 简洁的讲 如果一个样本在特定的空间中的K个最邻近的中的大多数属于某个类 则这个样本属于这个类 用途 k近邻的目的是测量不同特征值与数据集之间的距离来进行分类 样本差异性 欧式距离 优缺点 优点 精度高 对异常值不敏
  • Vue-后台返回excel文件流,前端怎么处理

    导出Excel公用方法 export function exportMethod data axios method data method url data url data params data params responseType
  • 总结:多线程集合类java.util.concurrent.*

    目录 一 java util concurrent Java并发工具包 二 阻塞队列BlockingQueue 2 1 BlockingQueue用法 2 2 BlockingQueue的方法 2 3 BlockingQueue的实现 2