启动线程的方法

1.extends Thread

  1. public static class AThread extends Thread{
  2. @Override
  3. public void run() {
  4. super.run();
  5. System.out.println("extends Thread");
  6. }
  7. }

2.implements Runnable

  1. private static class BThread implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("hhh");
  5. }
  6. }

3.implements Callable

带返回结果,使用方法也和前面两个不一样。需要FutureTask去包装起来。

  1. private static class CThread implements Callable{
  2. @Override
  3. public Object call() throws Exception {
  4. System.out.println("这是什么");
  5. return null;
  6. }
  7. }

三种启动方法使用

  1. public static void main(String[] args) {
  2. AThread thread = new AThread();
  3. thread.start();
  4. BThread bThread = new BThread();
  5. new Thread(bThread).start();
  6. CThread cThread = new CThread();
  7. FutureTask<String> futureTask = new FutureTask<>(cThread);
  8. new Thread(futureTask).start();
  9. }

线程间共享

java支持多个线程同时访问同一个对象或对象的成员变量,通过关键字synchronized修饰方法或者以同步块的形式来进行使用,可以确保多个线程在同一时间只能有一个线程操作对象。被称为内置锁机制。
对象锁和类锁
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象。类锁和对象锁之间也是互不干扰的。对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象。类锁和对象锁之间也是互不干扰的。

线程间协作

等待通知机制

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

等待和通知的标准范式

等待方遵循如下原则。
1)获取对象的锁。
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
image.png
通知方遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
image.png
在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法,进入wait()方法后,当前线程释放锁,在从wait()返回前,线程与其他线程竞争重新获得锁,notifyAll方法一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

notify和notifyAll应该用谁

尽可能用notifyall(),谨慎使用notify(),看代码。

ThreadLocal

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值, ThreadLocal往往用来实现变量在线程之间的隔离。
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
public final static ThreadLocal RESOURCE = new ThreadLocal();RESOURCE代表一个能够存放String类型的ThreadLocal对象。此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

显式锁

Lock接口和synchronized的比较

我们一般的Java程序是靠synchronized关键字实现锁功能的,使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。synchronized属于Java语言层面的锁,也被称之为内置锁。
synchronized这种机制,一旦开始获取锁,是不能中断的,也不提供尝试获取锁的机制。
而Lock是由Java在语法层面提供的,锁的获取和释放需要我们明显的去获取,因此被称为显式锁。并且提供了synchronized不提供的机制。
image.png

Lock接口和核心方法

在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。
image.png
image.png

可重入锁ReentrantLock、所谓锁的公平和非公平

而synchronized关键字隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平和非公平锁
如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。
ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。事实上,公平的锁机制往往没有非公平的效率高。原因是,在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。

读写锁ReentrantReadWriteLock

之前提到锁(synchronized和ReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量

Condition接口

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
用Lock和Condition实现等待通知
image.png