java核心技术 基础知识<集合&并发part1>

tech2024-07-19  58

文章目录

java核心技术 基础知识<集合&并发part1>9 泛型程序设计9.5 算法9.6 遗留的集合 14 并发14.2 中断线程14.3 线程状态14.4 线程属性14.5 同步14.5.3 锁对象14.5.4 条件对象14.5.5 synchronized 关键字14.5.6 同步阻塞14.5.7 监视器概念14.5.8 Volatile 域14.5.9 final 变量14.5.10 原子性14.5.11 死锁14.5.12 线程局部变量14.5.13 锁测试与超时14.5.14 读/ 写锁 14.6 阻塞队列

java核心技术 基础知识<集合&并发part1>

9 泛型程序设计

9.5 算法

排序和混排:

java.util.Collections static <T extends Comparable<? super T>> void sort( List<T> elements); // 归并排序 static void shuffle(List<?> elements) static void shuffle(List<?> elements, Random r) // 随即打乱 java.util.List<E> default void sort(Comparator<? super T> comparator) // list的排序 java.util.Comparator<T> static <T extends Comparable<? super T>> Comparator<T> reverseOrder() default Comparator<T> reversed( ) // 比较器逆序

二分搜索:

这个方法将返回这个键在列表中的索引, 如果在列表中不存在这个键将返回负值i。 在这种情况下,应该将这个键插人到列表索引 -i-1 的位置上, 以保持列表的有序性。

static <T extends Comparable<? super T>> int binarySearch( List<T>elements , T key) static <T> int binarySearch(List<T> elements, T key, Conparator<? super T> c) // 从有序列表中搜索一个键, 如果元素扩展了AbstractSequentialList 类, // 则采用线性查找,否则将采用二分查找 i = Collections.binarySearch(c, element) ; i = Collections.binarySearch(c, element, comparator);

其他简单算法:

使用简单算法的好处,读者可以很轻松了解作者的意图。例如:

for (int i = 0; i < words.size(); i++) if (words.get(i).equals("C++", j) words.set(i , "Java") ; // 可以用如下简单的一句话表达 Collections.repiaceAll ("C++", "Java") ; words.removeIf( w -> w.length() <= 3) ; // 删除短于3长度的单词 words.replaceAll(String::toLowerCase) ; // 把单词转小写

Collections: min max copy fill addAll replaceAll indexOfSubList lastIndexOfSubList swap reverse rotate frequency disjoint

Collection: removeIf

批操作: removeAll,删除出现的 retainAll,删除未出现的

集合和数组的转换:

集合to数组:

Object[] values = staff.toArray(); // yes String[] values = (String[]) staff.toArray(); // Error! String[] values = staff.toArray(new String[0]); // yes staff.toArray(new String[staff.size()]); // yes

数组to集合: Arrays.asList包装器。

HashSet<String> staff = new HashSet<>(Arrays.asList(values));

编写自己的算法: 如果编写自己的算法(实际上, 是以集合作为参数的任何方法),应该尽可能地使用接口, 而不要使用具体的实现。

如果编写了一个返回集合的方法,可能还想要一个返回接口,而不是返回类的方法, 因为这样做可以在日后改变想法,并用另一个集合重新实现这个方法。

9.6 遗留的集合

Hashtable: 与HashMap作用一样,接口一样。 与Vector类的方法一样,Hashtable也是同步的。如果对同步性或者遗留代码兼容性没有要求,就应该使用HashMap。如果需要并发,使用ConcurrentHashMap。

枚举: 遗留集合用Enumeration接口对元素进行遍历。和Iterator很类似。 hasMoreElement, nextElement。

C++注释:C++中迭代器作参数很普遍。Java中很少,因为传递集合比传递迭代器更明智。

java.util.Enumeration<E> java.util.Hashtable<E> java.util.Vector<E>

属性映射:

java.util.Properties

属性映射(property map) 是一个类型非常特殊的映射结构。它有下面3 个特性: • 键与值都是字符串。 • 表可以保存到一个文件中, 也可以从文件中加载。 • 使用一个默认的辅助表。

栈:

java.util.Stack<E>

位集: Java 平台的BitSet 类用于存放一个位序列(它不是数学上的集, 称为位向量或位数组更为合适)。如果需要高效地存储位序列(例如,标志)就可以使用位集。由于位集将位包装在字节里, 所以,使用位集要比使用Boolean 对象的ArrayList 更加高效。

BitSet 类提供了一个便于读取、设置或清除各个位的接口。使用这个接口可以避免屏蔽和其他麻烦的位操作。

java.util.BitSet bucketOfBits.get(i) bucketOfBits.set(i) bucketOfBits.clear(i)

14 并发

资料收集: java主线程结束和子线程结束之间的关系 Java 并发编程:核心理论 Java & Go 并发编程系列

java和golang对比:

java: java.util.concurrent.CountDownLatch golang: sync.WaitGroup

如何实现并发: 1 ) 将任务代码移到实现了Runnable 接口的类的run 方法中。这个接口非常简单, 只有一个方法:

public interface Runnable { void run()}

由于只有一个方法的接口又称为函数式接口,可以用lambda表达式:

Runnable r = () -> { task code };

2 ) 由Runnable 创建一个Thread 对象:

Thread t = new Thread(r);

3 ) 启动线程:

t.start();

注: 发生InterruptedException异常时,run方法将结束执行。 也可以extends Thread来构造子类对象,调用run,不过不推荐,应该将要并行运行的任务与运行机制解耦合。 不要用run方法,直接调run,只会执行同一个线程中的任务,Thread.start会创建一个执行run的新线程。

第三种方式是通过 Callable 和 Future 创建线程。

java.lang.Thread Thread(Runnable target) //构造一个新线程, 用于调用给定目标的run() 方法。 void start( ) // 启动这个线程, 将引发调用run() 方法。这个方法将立即返回, 并且新线程将并发运行。 void run( ) // 调用关联Runnable的run方法。 java.lang.Runnable void run( ) // 必须覆盖这个方法, 并在这个方法中提供所要执行的任务指令。

14.2 中断线程

线程终止于:

run方法的最后一条语句执行完后,return返回时出现方法中未捕获的异常stop方法(启用)

没有强制线程终止的方法,interrupt可以用来请求终止线程。 线程的中断状态将被置位。boolean标志,每个线程应该不时检查这个标记,以判断线程是否被中断。

线程主动获取中断状态 要想弄清中断状态是否被置位, 首先调用静态的Thread.currentThread 方法获得当前线程, 然后调用islnterrupted方法:

while (!Thread.currentThread().isInterrupted() && more work to do) { do more work }

线程被阻塞时(sleep或wait),被动调interrupt方法时,阻塞调用将会被Interrupted Exception打断。

delay时interrupt了,会抛出InterruptedException。

示例:将中断作为一个终止的请求,循环判断isInterrupted

Runnable r = () -> { try { while (!Thread.currentThread() .isInterrupted() && more work to do) { do more work } } catch(InterruptedException e) { // thread was interr叩ted during sleep or wait } finally { // cleanup, if required } // exiting the run method terminates the thread };

示例2:sleep时被打断,捕获InterruptedException。

Runnable r = () -> { try { while { more work to do) { do more work Thread.sleep(delay); } } catch(InterruptedException e) { // thread was interrupted during sleep } finally { // cleanup, if required } // exiting the run method terminates the thread };

底层的InterruptedException如何处理?

不要抑制在底层用Thread.currentThread().interrupt()来设置中断状态,调用者检测。更好选择 用throws InterruptedException标记方法,不采用try语句,让调用者捕获异常。

API:

java.lang.Thread void interrupt() static boolean isInterrupted() // 正在执行这一命令的线程是否中断?副作用:当前中断标志置位false boolean isInterrupted() // 测试线程是否被终止,不改变中断状态 static Thread currentThread() // 当前线程对象

14.3 线程状态

6种,New(新创建),Runnable(可运行),Blocked(被阻塞),Waiting(等待),Timed waiting(计时等待),Terminated(被终止)。

New: new Thread(r)之后,线程还未运行,一些基础工作。

Runnable: 调用start方法后,处于Runnable,操作系统给时间片就是运行,不给就不运行。 大多抢占时调度,或者yield、阻塞再让出线程。多处理器的及其可以有多个线程并行。

Blocked & Waiting & Timed waiting: 当获得内部的对象锁(不是java.util.concurrent)失败时,进入阻塞状态,直到其他线程释放锁,并且调度器允许本线程持有它时。

当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。Object.wait,或者Thread.join,或是java.util.concurrent库中的Lock或Condition时,就会出现这种情况。

有几个方法有一个超时参数。调用它们导致线程进人计时等待( timed waiting ) 状态。这一状态将一直保持到超时期满或者接收到适当的通知。

Terminated: run自然退出或者未捕获的异常。

API:

java.lang.Thread void join() void join(long millis) Thread.State getState() void stop() void suspend() void resume()

14.4 线程属性

优先级: 不要将程序构建为功能的正确性依赖于优先级。 API:

void setPriority(int newPriority) // 1-10 static void yield() // 导致当前执行线程处于让步状态。如果有其他的可运行线程具有至少与此线程同样高 // 的优先级,那么这些线程接下来会被调度。注意,这是一个静态方法。

守护线程: 守护线程的唯一用途是为其他线程提供服务。当只剩下守护线程时, 虚拟机就退出了。 守护线程应该永远不去访问固有资源, 如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

void setDaemon( boolean isDaemon )

未捕获异常处理器: run语句抛出的非受查异常的处理器。 可以用setUncaughtExceptionHandler 方法为任何线程安装一个处理器。也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler 为所有线程安装一个默认的处理器。

下图:fromJava:并发不易,先学会用

14.5 同步

解决同时对共享数据的存取问题。

javap -c -v XXX可以查看反编译,根据字节码分析并发会产生的问题。

两种机制防止代码块受并发访问干扰,一个synchronized关键字,一个ReentrantLock类。

14.5.3 锁对象

使用ReentrantLock保护代码的结构:

myLock.lock(); // a ReentrantLock object try { critical section // 临界区段 } finally { myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown }

Java锁–Lock实现原理(底层实现)

Interface Lock 实现Lock接口类: ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。

锁是可重入的,使用持有计数(hold count)来跟踪对lock方法的嵌套调用。

java.util.concurrent.locks.Lock // interface void lock( ) void unlock( ) java,util.concurrent.locks.ReentrantLock // implement ReentrantLock( ) ReentrantLock(boolean fair ) // 公平策略,慢

14.5.4 条件对象

使用一个条件对象(conditional variable)来管理那些已经获得了一个锁但是却不能做有用工作的线程。

一个锁对象可以有一个或多个相关的条件对象。你可以用newCondition方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映它所表达的条件的名字。

class Bank { private Condition sufficientFunds; public Bank() { // bankLock新建一个条件变量 sufficientFunds = bankLock.newCondition(); } }

注:

调await()会放弃锁,希望另一个线程signalAlll(),自己没有办法重新激活自身。专业叫法:进入该条件的等待集。锁可用时,不会马上解除阻塞(与Lock不同),直到另一个线程调用同一条件的signalAll()为止。signal()会随机选一个,有死锁风险。 java.util.concurrent.locks.Lock Condition newCondition( ) // 返回一个与该锁相关的条件对象。 java.util.concurrent.locks.Condition void await( ) // 将该线程放到条件的等待集中。 void signalAll( ) // 解除该条件的等待集中的所有线程的阻塞状态。 void signal( ) // 从该条件的等待集中随机地选择一个线程, 解除其阻塞状态。

总结一下 有关锁和条件的关键之处: • 锁用来保护代码片段, 任何时刻只能有一个线程执行被保护的代码。 • 锁可以管理试图进入被保护代码段的线程。 • 锁可以拥有一个或多个相关的条件对象。 • 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

14.5.5 synchronized 关键字

每一个对象都有一个内部锁(对象锁)。如果一个方法用synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法, 线程必须获得内部的对象锁。 如果当前线程不是对象锁的持有者(和reentrantLock不同之处),会抛出IllegalMonitorStateException。

java.lang.Object void notifyAll() // 解除同对象调wait的阻塞状态,只能在synchronized方法块中调用 // 如果当前线程不是对象锁的持有者,会抛出IllegalMonitorStateException void notify() // 随机选一个 void wait() // 等待直到被通知。如果当前线程不是对象锁的持有者,会抛出IllegalMonitorStateException void wait(long millis) void wait(long millis, int nanos) // 等待直到被通知。 // 或者经过指定的时间。

等价代码:

public synchronized void method() { method body } ===============等价============ public void method() { this.intrinsicLock.lock(); try { method body } finally { this.intrinsicLock.unlock(); } }

14.5.6 同步阻塞

sychronized应用于一个对象,同步代码块。

上一节介绍调用synchronized方法获得锁。应用于对象,加锁代码块 synchronized (obj) // this is the syntax for a synchronized block { critical section }

有时程序员使用一个对象的锁来实现额外的原子操作, 实际上称为客户端锁定( client side locking) 。相比于用synchronized方法。

客户端锁定是非常脆弱的,通常不推荐使用。因为依赖于代码块里面(critical section里面)依然的不会有竞争条件的。

14.5.7 监视器概念

如果一个方法用synchronized 关键字声明,那么,它表现的就像是一个监视器方法。通过调用 wait / notifyAll / notify 来访问条件变量。

14.5.8 Volatile 域

Java并发编程:volatile关键字解析

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。禁止进行指令重排序。

14.5.9 final 变量

final声明域时,其他线程会在构造函数完成构造之后才看到这个final修饰的accounts 变量。 如果不使用final,就不能保证其他线程看到的是accounts 更新后的值,它们可能都只是看到null , 而不是新构造的HashMap。

14.5.10 原子性

高效的机器级指令保证操作的原子性。 如果有大量线程要访问相同的原子值, 性能会大幅下降, 因为乐观更新需要太多次重试。

java.util.concurrent.atomic // 类 AtomicInteger AtomicIntegerArray AtomicIntegerFieldUpdater AtomicLong LongAdder // 可以有多个线程更新不同的加数,线程个数增加时会自动提供新的加数。 LongAccumulator // 方法 incrementAndGet() decrementAndGet() compareAndSet(oldV, newV) // CAS updateAndGet() // 返回新值 accumulateAndGet() // 返回新值 getAndUpdate() // 返回原值 getAndAccumulate() // 返回原值

14.5.11 死锁

当程序挂起时, 键入CTRL+\, 将得到一个所有线程的列表。每一个线程有一个栈踪迹, 告诉你线程被阻塞的位置。

14.5.12 线程局部变量

避免共享变量的方式:使用ThreadLocal辅助类为各个线程提供各自的实例。

java.lang.ThreadLocal<T> T get() // 得到这个线程的当前值。如果是首次调用get, 会调用initialize 来得到这个值。 protected initialize( ) // 应覆盖这个方法来提供一个初始值。默认情况下,这个方法返回null void set(T t) void remove( ) static <S> ThreadLocal<S> withInitial (Supplier<? extends S> supplier) // 创建一个线程局部变量,其初始值通过调用给定的supplier 生成。 // 使用方式 public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); // 访问 dateFormat.get().format(new Date())

14.5.13 锁测试与超时

尝试加锁,不成功立即离开去做其他事。 多路复用(类似golang select的default分支,不会阻塞)。带上timeout的类似 golang中<- time.After(xxx)分支。

如果调用带有用超时参数的tryLock, 那么如果线程在等待期间被中断,将抛出InterruptedException 异常。这是一个非常有用的特性,因为允许程序打破死锁。

java.util.concurrent.locks.Lock boolean tryLock() // 尝试获得锁而没有发生阻塞; boolean tryLock(long time, TimeUnit unit) // 尝试获得锁,阻塞时间不会超过给定的值 void lockInterruptibly() // 它就相当于一个超时设为无限的tryLock 方法 java.util.concurrent.locks.Condition boolean await(long time , TimeUnit unit ) // 带超时的await void awaitUninterruptibly() //

14.5.14 读/ 写锁

和其他语言一样,同一资源,读可以同时,但是写行为和其他互斥。 ReentrantReadWriteLock就是读写锁。可分别获得读锁、写锁。

java.util.concurrent.locks.ReentrantReadWriteLock Lock readLock( ) // 得到一个可以被多个读操作共用的读锁, 但会排斥所有写操作。 Lock writeLock( ) // 得到一个写锁, 排斥所有其他的读操作和写操作。

14.6 阻塞队列

并发程序的高级特性。较高层次,更方便、更安全。远离前面介绍的底层结构。

在协调多个线程之间的合作时, 阻塞队列是一个有用的工具。类似于golang的channel。 【Java&Go并发编程系列】1.阻塞队列与通道——BlockingQueue VS channel

包前缀:java.util.concurrent.XXX ArrayBlockingQueue<E> // 循环数组实现 LinkedBlockingQueue<E> // 链表实现 LinkedBlockingDeque<E> // 链表实现双端 DelayQueue<E extends Delayed> // 延迟队列 // 只有那些延迟已经超过时间的元素可以从队列中移出。 Delayed PriorityBlockingQueue<E> // 按优先级 BlockingQueue<E> // interface // implement by ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue // 带时间限制的offer和poll BlockingDeque<E> // implement by LinkedBlockingDeque TransferQueue<E> // implement by LinkedTransferQueue void transfer(E element) boolean tryTransfer(E element, long time, TimeUnit unit)

add element remove 增、取、删并取,异常抛出IllegalStateException或则NoSuchElementException offer peek poll 增、取、删并取,异常返回boolean或null put take 增、取, 取不到阻塞

注释: poll 和peek 方法返回空来指示失败。因此,向这些队列中插入null 值是非法的。

JavaSE 7 增加了一个TransferQueue接口,允许生产者线程等待, 直到消费者准备就绪可以接收一个元素。(golang 阻塞发送者)

最新回复(0)