java数据结构类--笔记

程序的灵魂是算法。提到算法,肯定离不开数据结构。今天就聊一聊java中的和数据结构相关的类。

java.util包

在这里插入图片描述
在这里插入图片描述

concurrent包

里面是和并发编程相关的类
主要是:原子类–锁--并发的数据结构类【队列,链表,哈希…】
在这里插入图片描述
atomic—原子类【https://blog.csdn.net/boom_man/article/details/78352722
locks—锁

Lock锁和同步块的区别

1.同步块只能包含在一个方法内----而lock()和unlock()操作却可以在跨越多个不同的方法使用。

2.同步块不支持公平性,任一个线程都能获取已经被释放的锁,不能指定优先权。但我们却可以使用Lock API指定公平属性从而实现公平性。它能确保等待时间最长的线程优先获取锁。

3.当一个线程不能访问同步块时,它会被阻塞住。而 Lock API提供的有 tryLock()方法,使用该方法,只有在锁不被其他线程持有且可用时,才会真正获取锁。这将极大地降低阻塞时间。

4.那些获取访问同步块的等待线程不能被中断,Lock API提供了一个 lockInterruptbly()方法,当线程正在等待锁时,该方法可以用于中断该线程。’

接口

ReadWriteLock读写锁

ReadWriteLock维护一对关联锁,一个用于只读操作,一个用于写入。只要没有写入程序,读锁可以由多个读线程同时保持。写锁是独占的。

所有ReadWriteLock实现都必须保证writeLock操作(如锁接口中指定的)的内存同步效果也适用于关联的readLock。也就是说,成功获取读锁的线程将看到在先前释放写锁时所做的所有更新。

与互斥锁相比,读写锁在访问共享数据时允许更高级别的并发性。它利用这样一个事实:虽然一次只有一个线程(writer线程)可以修改共享数据,但在许多情况下,任何数量的线程都可以并发地读取数据(因此读线程)。理论上,读写锁的使用所允许的并发性的增加将导致使用互斥锁的性能改进。实际上,只有在多处理器上才能完全实现这种并发性的提高,而且只有在共享数据的访问模式合适的情况下才能实现。



读写锁是否会比互斥锁的使用提高性能取决于数据的读取频率(与被修改的频率相比)、读写操作的持续时间以及数据的争用情况(即,同时尝试读取或写入数据的线程数)。例如,一个集合最初填充了数据,然后在频繁搜索(例如某种目录)时很少修改,是使用读写锁的理想候选对象。但是,如果更新变得频繁,那么数据的大部分时间都被独占锁定,并发性几乎没有任何增加。此外,如果读取操作太短,则读写锁实现的开销(其本质上比互斥锁更复杂)可以支配执行成本,特别是因为许多读写锁实现仍然通过一小段代码序列化所有线程。最后,只有分析和测量才能确定读写锁的使用是否适合您的应用程序。



尽管读写锁的基本操作是直接的,但是实现必须做出许多策略决策,这可能会影响给定应用程序中读写锁的有效性。这些政策的例子包括:



在读写器释放写锁时,当读写器都在等待时,确定是授予读锁还是写锁。写作者偏好是很常见的,因为写的时间很短,而且很少。读者偏好不太常见,因为如果读者像预期的那样频繁和长寿,则可能导致写作的长时间延迟。公平的,或者“按顺序”的实现也是可能的。

确定在读卡器处于活动状态且编写器正在等待时请求读锁的读卡器是否被授予读锁。对读卡器的偏好可以无限期地延迟写入器,而对写入器的偏好可以降低并发的可能性。

确定锁是否可重入:具有写锁的线程能否重新获取它?它能在保持写锁的同时获得读锁吗?读锁本身是否可重入?

在不允许插入写入程序的情况下,可以将写锁降级为读锁吗?相对于其他等待的读写器,读锁是否可以升级为写锁?

在评估给定实现对应用程序的适用性时,应该考虑所有这些因素。

lock

锁实现提供了比使用同步方法和语句更广泛的锁操作。它们允许更灵活的结构,可以具有完全不同的属性,并且可以支持多个关联条件对象。

锁是一种工具,用于控制多个线程对共享资源的访问。通常,锁提供对共享资源的独占访问:一次只有一个线程可以获取锁,对共享资源的所有访问都要求首先获取锁。但是,某些锁可能允许并发访问共享资源,例如读写锁的读锁。



使用同步方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但强制所有锁的获取和释放都以块结构化的方式发生:当获取多个锁时,它们必须以相反的顺序释放,并且所有锁必须在相同的词法范围内释放。是后天习得的。



虽然同步方法和语句的作用域机制使得使用监视器锁编程更容易,并且有助于避免涉及锁的许多常见编程错误,但有时需要更灵活地处理锁。例如,一些遍历并发访问数据结构的算法需要使用“移交”或“链锁”:先获取节点A的锁,然后获取节点B,然后释放A和获取C,然后释放B和获取D,等等。锁接口的实现允许在不同的作用域中获取和释放锁,并允许以任何顺序获取和释放多个锁,从而允许使用此类技术。



这种灵活性的增加带来了额外的责任。缺少块结构锁定将删除同步方法和语句所发生的锁的自动释放。在大多数情况下,应使用以下成语:

Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
 } finally {
   l.unlock();
 }

当锁定和解锁发生在不同的作用域中时,必须注意确保在锁定期间执行的所有代码都受到try finally或try catch的保护,以确保在必要时释放锁定。

锁实现通过提供获取锁的非阻塞尝试(tryLock())、获取可中断锁的尝试(lockInterruptibly())和获取可超时锁的尝试(tryLock(long,TimeUnit))来提供使用同步方法和语句的附加功能。



锁类还可以提供与隐式监视锁完全不同的行为和语义,例如保证顺序、不可重入使用或死锁检测。如果一个实现提供了这样的专门语义,那么该实现必须记录这些语义。



请注意,锁实例只是普通对象,它们本身可以用作synchronized语句中的目标。获取锁实例的监视器锁与调用该实例的任何lock()方法都没有指定的关系。为了避免混淆,建议您不要以这种方式使用锁实例,除非在它们自己的实现中。



除非另有说明,否则为任何参数传递空值将导致引发NullPointerException。



内存同步

所有锁实现都必须强制执行内置监视器锁提供的相同内存同步语义,如Java语言规范(17.4内存模型)中所述:



成功的锁定操作与成功的锁定操作具有相同的内存同步效果。

成功的解锁操作与成功的解锁操作具有相同的内存同步效果。

不成功的锁定和解锁操作以及可重入的锁定/解锁操作不需要任何内存同步效果。

实施注意事项

锁获取的三种形式(可中断、不可中断和定时)在性能特征、顺序保证或其他实现质量方面可能有所不同。此外,在给定的锁类中,中断正在进行的锁的获取的能力可能不可用。因此,实现不需要为所有三种锁获取形式定义完全相同的保证或语义,也不需要支持正在进行的锁获取的中断。需要一个实现来清楚地记录每个锁定方法提供的语义和保证。它还必须遵守此接口中定义的中断语义,只要支持锁获取中断:要么完全中断,要么仅中断方法入口。



由于中断通常意味着取消,并且对中断的检查通常很少,所以实现可以支持

Condition

条件将对象监视器方法(wait、notify和notifyAll)分解为不同的对象,通过将它们与使用任意锁实现相结合,使每个对象具有多个等待集的效果。当锁替换同步方法和语句的使用时,条件将替换对象监视器方法的使用。

条件(也称为条件队列或条件变量)为一个线程提供了一种暂停执行(等待)的方法,直到另一个线程通知某些状态条件现在可能为真。由于对共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁与该条件相关联。等待条件提供的键属性是,它自动释放关联的锁并挂起当前线程,就像Object.wait一样。



条件实例本质上绑定到锁。要获取特定锁实例的条件实例,请使用其newCondition()方法。



例如,假设我们有一个有界缓冲区,它支持put和take方法。如果在空缓冲区上尝试执行take,则线程将阻塞,直到某个项变为可用;如果在完全缓冲区上尝试执行put,则线程将阻塞,直到某个空间变为可用。我们希望保持等待,将线程放入单独的等待集中,以便在缓冲区中的项目或空间可用时,我们可以使用仅通知单个线程的优化。这可以通过两个条件实例来实现。



class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }



(ArrayBlockingQueue类提供此功能,因此没有理由实现此示例用法类。)

条件实现可以提供不同于对象监视器方法的行为和语义,例如保证通知的顺序,或者在执行通知时不需要持有锁。如果一个实现提供了这样的专门语义,那么该实现必须记录这些语义。



请注意,条件实例只是普通对象,它们本身可以用作synchronized语句中的目标,并且可以调用它们自己的监视器等待和通知方法。获取条件实例的监视锁或使用其监视方法与获取与该条件相关联的锁或使用其等待和信令方法没有指定的关系。为了避免混淆,建议您不要以这种方式使用条件实例,除非可能是在它们自己的实现中。



除非另有说明,否则为任何参数传递空值将导致引发NullPointerException。



实施注意事项

在等待条件时,通常允许出现“虚假唤醒”,作为对底层平台语义的让步。这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待条件,测试正在等待的状态谓词。实现可以自由地消除虚假唤醒的可能性,但建议应用程序程序员始终假设它们可能发生,因此始终在循环中等待。



条件等待的3种形式(可中断、不可中断和定时)在某些平台上的易实现性和性能特征可能有所不同。特别是,可能很难提供这些特性并维护特定的语义,例如排序保证。此外,在所有平台上实现中断线程实际挂起的能力并不总是可行的。



因此,实现不需要为所有三种等待形式定义完全相同的保证或语义,也不需要支持中断线程的实际挂起。



一个实现需要清楚地记录每个等待方法提供的语义和保证,当一个实现确实支持中断线程挂起时,它将
发布了437 篇原创文章 · 获赞 82 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_41063141/article/details/103608871
今日推荐