java并发编程知识

一、Thread和Runnable

1.创建Thread和Runnable对象

Runnable qw = () -> System.out.println("qw");

2.获取和设置线程名称

getName()

存活状态:isAlive()

线程的执行状态:getState()

Thread.State枚举常量标识:


NEW:
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

线程的优先级:getPriority()

线程的守护线程状态:isDaemon()

setDaemon(true)

2.操作更高级的线程任务

1.中断线程

void intterupt()

static boolean interrupted()

boolean isInterrupted()

2.等待线程

void join()

void join(long millis)

void join(long millis,int nanos)

3.线程睡眠

void sleep(long millis)

void sleep(long millis,int nanos)

二、同步

1.线程中的问题:

竞态条件

数据竞争

缓存变量

2.同步临界区的访问

2.1.使用同步方法

2.2.使用同步块

3.谨防活跃性问题

死锁:线程1等待线程2互斥持有的资源,而线程2也在等待线程1互斥持有的资源。

活锁:线程x持续重试一个总是失败的操作,以致于无法继续执行

饿死:线程x一直被(调度器)延迟访问其赖以执行的资源

4.volatile和Final变量

5.等待/通知API

void wait():导致当前线程一直处于等待,直到另外的线程调用这个对象的notify()或者notifyAll()方法,又或者一直等待其他的线程中断当前等待的线程

void wait(long timeout):

void wait(long timeout,int nanos):

void notify():唤醒正在等待该对象监听器的单条线程。

void notifyAll():唤醒正在等待该对象监听器的全部线程。

6.生产者和消费者

public class Test3 {
   public static void main(String[] args) {
      Share s=new Share();
      Producer p=new Producer(s);
      Consumer c=new Consumer(s);
      p.start();
      c.start();
  }

}
class Share{
private char c;
private volatile boolean writeable=true;
   synchronized void setChar(char c){
      while (!writeable){
          try {
              wait();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
      this.c=c;
      writeable=false;
      notify();
  }
   synchronized char getChar(){
       while (writeable){
           try {
               wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       writeable=true;
       notify();
       return c;
  }
}
class Producer extends Thread{
  private  final Share s;
  public Producer(Share s){
      this.s=s;
  }
  public void run(){
      for(char ch='A';ch<='Z';ch++){
          s.setChar(ch);
          System.out.println(ch+" produce by producer");
      }
  }
}
class Consumer extends Thread{
   private  final Share s;
   public Consumer(Share s){
       this.s=s;
  }
   public void run(){
       char ch;
       do{
           ch=s.getChar();
           System.out.println(ch+" consume by consumer");
      }while (ch!='Z');
  }
}

三、同步器

1.倒计时门闩CountDownLatch

void await():除非线程被中断,否则强制调用线程一直等到计数倒数到0.

boolean await(long timeout,TimeUnit unit):除非线程被中断,否则强制调用线程一直等到计数倒数到0或者以unit作为时间单位的timeout超时

void countDown():递减计数,当计数降至0时,释放所有等待线程

long getCount():返回当前的计数,该方法对于测试和调试很有用。

String toString():返回一条标识这个门闩及其状态的字符串


final static int READS=3;
   public static void main(String[] args) {
       CountDownLatch start=new CountDownLatch(1);
       CountDownLatch done=new CountDownLatch(READS);
       Runnable runnable = () -> {
           try {
               report("11");
               start.await();
               report("22");
               Thread.sleep(10*1000);
               done.countDown();
               System.out.println("66");
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      };
       ExecutorService executorService = Executors.newFixedThreadPool(READS);
       for(int i=0;i<READS;i++){
           executorService.execute(runnable);
      }
       System.out.println("33");
       try {
           Thread.sleep(1000);
           start.countDown();
           System.out.println("44");
           done.await();
           System.out.println("55");
           executorService.shutdown();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

  }
   static void report(String s){
       System.out.println(System.currentTimeMillis()+":"+Thread.currentThread()+":"+s);
  }

2.同步屏障CyclicBarrier

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。


public class CyclicBarrierTest {
   // 自定义工作线程
   private static class Worker extends Thread {
       private CyclicBarrier cyclicBarrier;

       public Worker(CyclicBarrier cyclicBarrier) {
           this.cyclicBarrier = cyclicBarrier;
      }

       @Override
       public void run() {
           super.run();

           try {
               System.out.println(Thread.currentThread().getName() + "开始等待其他线程");
               cyclicBarrier.await();
               System.out.println(Thread.currentThread().getName() + "开始执行");
               // 工作线程开始处理,这里用Thread.sleep()来模拟业务处理
               Thread.sleep(1000);
               System.out.println(Thread.currentThread().getName() + "执行完毕");
          } catch (Exception e) {
               e.printStackTrace();
          }
      }
  }

   public static void main(String[] args) {
       int threadCount = 3;
       CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount,new BarrierAction());

       for (int i = 0; i < threadCount; i++) {
           System.out.println("创建工作线程" + i);
           Worker worker = new Worker(cyclicBarrier);
           worker.start();
      }
  }
}
class BarrierAction implements Runnable{
   public void run(){
       System.out.println("barrier");
  }
}

3.交换器Exchanger

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的。

​ Exchanger类提供了两个方法,String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange;String exchange(V x,long timeout,TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange,并且设置最大等待时间,当等待时间超过timeout便停止等待。


public class ExchangerTest {
   public static void main(String[] args) {
       ExecutorService executor = Executors.newCachedThreadPool();

       final Exchanger exchanger = new Exchanger();
       executor.execute(new Runnable() {
           String data1 = "克拉克森,小拉里南斯";


           @Override
           public void run() {
               nbaTrade(data1, exchanger);
          }
      });


       executor.execute(new Runnable() {
           String data1 = "格里芬";

           @Override
           public void run() {
               nbaTrade(data1, exchanger);
          }
      });

       executor.execute(new Runnable() {
           String data1 = "哈里斯";

           @Override
           public void run() {
               nbaTrade(data1, exchanger);
          }
      });

       executor.execute(new Runnable() {
           String data1 = "以赛亚托马斯,弗莱";

           @Override
           public void run() {
               nbaTrade(data1, exchanger);
          }
      });

       executor.shutdown();
  }

   private static void nbaTrade(String data1, Exchanger exchanger) {
       try {
           System.out.println(Thread.currentThread().getName() + "在交易截止之前把 " + data1 + " 交易出去");
           Thread.sleep((long) (Math.random() * 1000));

           String data2 = (String) exchanger.exchange(data1);
           System.out.println(Thread.currentThread().getName() + "交易得到" + data2);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }
}

4.信号量Semaphore

 Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

1.工作原理        以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。这个停车系统中,每辆车就好比一个线程,看门人就好比一个信号量,看门人限制了可以活动的线程。假如里面依然是三个车位,但是看门人改变了规则,要求每次只能停两辆车,那么一开始进入两辆车,后面得等到有车离开才能有车进入,但是得保证最多停两辆车。对于Semaphore类而言,就如同一个看门人,限制了可活动的线程数。

Semaphore主要方法:

Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。

Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。

void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。

void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。

void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。

void release(int n):释放n个许可。

int availablePermits():当前可用的许可数。


public class SemaphoreTest {
   private static final Semaphore semaphore=new Semaphore(3);
   private static final ThreadPoolExecutor threadPool=new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());

   private static class InformationThread extends Thread{
       private final String name;
       private final int age;
       public InformationThread(String name,int age)
      {
           this.name=name;
           this.age=age;
      }

       public void run()
      {
           try
          {
               semaphore.acquire();
               System.out.println(Thread.currentThread().getName()+":大家好,我是"+name+"我今年"+age+"岁当前时间为:"+System.currentTimeMillis());
               Thread.sleep(1000);
               System.out.println(name+"要准备释放许可证了,当前时间为:"+System.currentTimeMillis());
               System.out.println("当前可使用的许可数为:"+semaphore.availablePermits());
               semaphore.release();

          }
           catch(InterruptedException e)
          {
               e.printStackTrace();
          }
      }
  }
   public static void main(String[] args)
  {
       String[] name= {"李明","王五","张杰","王强","赵二","李四","张三"};
       int[] age= {26,27,33,45,19,23,41};
       for(int i=0;i<7;i++)
      {
           Thread t1=new InformationThread(name[i],age[i]);
           threadPool.execute(t1);
      }
       threadPool.shutdown();
  }

}

5.Phaser

java多线程技术提供了Phaser工具类,Phaser表示“阶段器”,用来解决控制多个线程分阶段共同完成任务的情景问题。其作用相比CountDownLatch和CyclicBarrier更加灵活,例如有这样的一个题目:5个学生一起参加考试,一共有三道题,要求所有学生到齐才能开始考试,全部同学都做完第一题,学生才能继续做第二题,全部学生做完了第二题,才能做第三题,所有学生都做完的第三题,考试才结束。分析这个题目:这是一个多线程(5个学生)分阶段问题(考试考试、第一题做完、第二题做完、第三题做完),所以很适合用Phaser解决这个问题。

四、锁

1.锁

  • lock():获取不到锁就不罢休,否则线程一直处于block状态。

  • tryLock():尝试性地获取锁,不管有没有获取到都马上返回,拿到锁就返回true,不然就返回false

  • tryLock(long time, TimeUnit unit):如果获取不到锁,就等待一段时间,超时返回false。


  • lockInterruptibly()

    :该方法稍微难理解一些,在说该方法之前,先说说线程的中断机制,每个线程都有一个中断标志,不过这里要分两种情况说明:

    1. 线程在sleepwait或者join, 这个时候如果有别的线程调用该线程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException

    2. 如果线程处在运行状态, 则在调用该线程的interrupt()方法时,不会响应该中断。lockInterruptibly()和上面的第一种情况是一样的, 线程在获取锁被阻塞时,如果调用lockInterruptibly()方法,该线程会被唤醒并被要求处理InterruptedException。下面给出一个响应中断的简单例子:

2.重入锁ReentrantLock

jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

ReentrantLock和synchronized的相同点:

  • 1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

  • 2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ReentrantLock相比synchronized的额外功能:

1.ReentrantLock可以实现公平锁。

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。而非公平锁则随机分配这种使用权。和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。在创建ReentrantLock的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁


ReentrantLock lock = new ReentrantLock(true);

2.ReentrantLock可响应中断

当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()。该方法可以用来解决死锁问题。

3.获取锁时限时等待

ReentrantLock还给我们提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以使用该方法配合失败重试机制来更好的解决死锁问题。

结合Condition实现等待通知机制:

1.使用synchronized结合Object上的wait和notify方法可以实现线程间的等待通知机制。ReentrantLock结合Condition接口同样可以实现这个功能。而且相比前者使用起来更清晰也更简单。


static Condition notEmpty = lock.newCondition();

static Condition notFull = lock.newCondition();

Condition接口在使用前必须先调用ReentrantLock的lock()方法获得锁。之后调用Condition接口的await()将释放锁,并且在该Condition上等待,直到有其他线程调用Condition的signal()方法唤醒线程。使用方式和wait,notify类似。

2.使用Condition实现简单的阻塞队列

阻塞队列是一种特殊的先进先出队列,它有以下几个特点1.入队和出队线程安全2.当队列满时,入队线程会被阻塞;当队列为空时,出队线程会被阻塞。

3.读写锁ReadWriteLock

1、ReadWriteLock接口

   首先,得明确一点,ReadWriteLock接口并没有继承Lock接口,可参考上一篇文章的继承结构图,ReadWriteLock 仅仅定义了两个方法,即readLock、writeLock方法;Lock readLock( ): 返回用于读取操作的锁:Lock writeLock( ): 返回用于写入操作的锁。

2、 读-写锁的性能

   与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。但在实践中,只有在多处理器上频繁地访问读取数据结构,才能提高性能。 而在其他情况下,读-写锁的性能却比独占锁的性能要差一点,这是因为读-写锁的复杂性更高。所以要对程序进行分析,判断读-写锁是否能提高性能。

3、ReadWriteLock接口实现时的可选策略

尽管读-写锁的基本操作是直截了当的,但实现仍然必须作出许多决策,这些决策可能会影响给定应用程序中读-写锁的效果。这些策略的例子包括:

  • 释放优先: 当一个写入操作释放写锁时,并且队列中同时存在读线程和写线程时,那么是读线程优先获得锁,还是写线程,或者说是最先发出请求的线程

  • 读线程插队: 如果当读线程持有着读锁时,有写线程在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插队到线程前面,那么将提高并发性,但却可能造成写线程发生饥饿问题。

  • 重入性: 读锁、写锁是否允许重入。

  • 降级: 如果一个线程持有写锁,那么它能否在不释放锁的情况下降级成一个读锁?

  • 升级: 拥有读锁的线程能否优于其他正在等待的读线程和写线程而升级成为一个写锁?在大多数的读-写锁实现中并不支持升级,因为很容易造成死锁(如果两个读线程同时升级为写锁,那么二者都不会释放读取锁)

4.重入读写锁ReentrantReadWriteLock

ReentrantReadWriteLock实现了ReadWriteLock接口。

1、ReentrantReadWriteLock的实现策略:

  • 可重入的加锁

  • 提供公平锁与非公平锁(默认)的选择。与ReentrantLock类似,都是在构造方法中传入参数来决定,关于公平锁与非公平锁的详细可参考我的上一篇博文;

  • 读线程不能插队: 尽管当读线程持有着读锁时,写线程等待获取锁,这时候新到达的其他读线程都必须等待它们前面的写线程使用完并释放了写锁,才能获得读锁。

  • 写线程可以降级为读线程(支持降级),但是读线程不能升级为写线程(不支持升级);

2、ReentrantReadWriteLock 的读锁与写锁

ReentrantReadWriteLock实现的是ReadWriteLock接口,并没有实现Lock接口,但其管理的读锁ReentrantReadWriteLock.ReadLock 、写锁ReentrantReadWriteLock.WriteLock都是其内部类,并且是实现Lock接口。注意以下两点:

  • 读锁、写锁都支持定时获取锁、中断锁、非阻塞获取锁,与ReetrantLock相似;

  • Condition 支持 :只能用于写锁,读锁是不支持的(因为读锁是共享锁)。写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException;

3、ReentrantReadWriteLock 提供的监视系统状态的方法

boolean hasQueuedThread(Thread thread):查询是否给定线程正在等待获取读取或写入锁。注意,因为随时可能发生取消操作,所以返回 true 并不保证此线程将获取锁。此方法主要用于监视系统状态。boolean hasQueuedThreads( ):查询是否所有的线程正在等待获取读取或写入锁。注意,因为随时可能发生取消操作,所以返回 true 并不保证任何其他线程将获取锁。此方法主要用于监视系统状态。boolean hasWaiters(Condition condition)查询是否有些线程正在等待与写入锁有关的给定条件。注意,因为随时可能发生取消操作,所以返回 true 并不保证任何其他线程将获取锁。此方法主要用于监视系统状态。boolean isFair( )如果此锁将公平性设置为 ture,则返回 true。boolean isWriteLocked( )查询是否某个线程保持了写入锁。boolean isWriteLockedByCurrentThread( )查询当前线程是否保持了写入锁。

int getWaitQueueLength(Condition condition)返回正等待与写入锁相关的给定条件的线程估计数目。注意,因为随时可能发生超时和中断,所以只能将估计值作为实际等待线程数的上限。此方法设计用于监视系统状态,而不是同步控制。int getWriteHoldCount( )查询当前线程在此锁上保持的重入写入锁数量。int getQueueLength( )返回等待获取读取或写入锁的线程估计数目。int getReadHoldCount( )查询当前线程在此锁上保持的重入读取锁数量。int getReadLockCount( )查询为此锁保持的读取锁数量。还有几个protected方法,不再详述。


public class ReentrantReadWriteLockTest {
   public static int count = 5;

   public static void main(String[] args) {
       //创建一个非公平的读写锁
       ReadWriteLock lock = new ReentrantReadWriteLock(false);

       Thread threadA = new Thread("threadA"){
           @Override
           public void run() {
               //获取读锁
               lock.readLock().lock();
               System.out.println("成功获取读锁,count的值是:"+count);
               if(count<10){
                   lock.readLock().unlock();
                   //在获取写锁前,必须先释放读锁
                   lock.writeLock().lock();
                   System.out.println("成功获取写锁");
                   count += count*3;

                   //获取读锁,此时没有释放写锁,即为写锁降级为读锁
                   lock.readLock().lock();
                   //成功获取读锁,写锁降级成功,释放写锁
                   lock.writeLock().unlock();
                   System.out.println("写锁成功降级成读锁,count的值是:"+count);
              }
          }
      };
       threadA.start();
  }
}

五、并发工具类

1.并发集合

1.BlockingQueue

BlockingQueue实现类和继承接口:

ArrayBlockingQueue

DelayQueue

LinkedBlockingQueue

PriorityBlockingQueue

SynchronousQueue

2.ConcurrentHashMap

2.原子变量

3.Fork/join框架

4.Completion Service

 

原文地址:https://www.cnblogs.com/mufeng07/p/12626269.html