Java基础回顾-03

一 并发

1 共享内存

每一个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈,但线程之间可以共享内存,它们可以访问和操作相同的对象。

import java.util.ArrayList;
import java.util.List;

public class ShareMemoryDemo {
    private static int shared = 0;
    private static void incrShared() {
        shared++;
    }
    static class ChildThread extends Thread {
        List<String> list;
        public ChildThread(List<String> list) {
            this.list = list;
        }

        @Override
        public void run() {
            incrShared();
            list.add(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        Thread t1 = new ChildThread(list);
        Thread t2 = new ChildThread(list);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(shared);
        System.out.println(list);
    }
}

2
[Thread-0, Thread-1]

大部分情况下,会输出期望的值。当多条执行流可以操作相同的变量时,可能会出现一些意料之外的结果,包括竞态条件和内存可见性问题。

1.1 竞态条件(race condition)

当多个线程访问和操作同一个对象时,最终执行结果与执行顺序相关,可能正确也可能不正确。

public class CounterThread extends Thread {
    private static int counter = 0;

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            threads[i] = new CounterThread();
            threads[i].start();
        }
        for (int i = 0; i < num; i++) {
            threads[i].join();
        }
        System.out.println(counter);
    }
}

共享静态变量变量counter,初始值位0,在main方法中创建了1000个线程,每个线程对counter循环加1000次,main线程等待所有线程结束后输出counter的值。期望结果为100万。但输出:

999601

且每次结果都不一样。因为counter++这个操作不是原子操作,分为三个步骤:

  1. 取counter的当前值
  2. 在当前值基础上加1
  3. 将新值重新赋值给counter

怎样解决问题:

  1. 使用synchronized关键字
  2. 使用显式锁
  3. 使用原子变量

1.2 内存可见性

多个线程可以共享访问和操作相同的变量,但是一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到。

public class VisibilityDemo {
    private static boolean shutdown = false;
    static class HelloThread extends Thread {
        @Override
        public void run() {
            while (!shutdown) {
                // do something
            }
            System.out.println("exit hello");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new HelloThread().start();
        Thread.sleep(1000);
        shutdown = true;
        System.out.println("exit main");
    }
}

期望的结果是两个线程都退出,但实际执行时,很可能会发现HelloThread永远都不会退出,也就是说,在HelloThread执行流来看,shutdown永远为false,即是main线程已经更改为了true。

一个线程对内存的修改,另一个线程看不到,一是修改没有及时同步到内存,二是另一个线程根本就没有从内存读。

解决这个问题:

  1. 使用volatile
  2. 使用synchronized关键字或显式锁同步

2 理解synchronized

2.1 实例方法

synchronized实例方法实际保护的是同一个对象的方法调用,确保同时只能有一个线程执行。

public class CounterThread extends Thread {
    Counter counter;
    public CounterThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.incr();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Counter counter = new Counter();
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            threads[i] = new CounterThread(counter);
            threads[i].start();
        }
        for (int i = 0; i < num; i++) {
            threads[i].join();
        }
        System.out.println(counter.getCount());
    }
}

再具体说,synchronized实例方法保护的是当前实例对象,即this,this对象有一个锁和一个等待队列,锁只能被一个线程持有,其他试图获得同样锁的线程需要等待。执行synchronized实例方法的过程大致如下:

  1. 尝试获得锁,如果能够获得锁,继续下一步,否则加入等待队列,阻塞并等待唤醒。
  2. 执行实例方法体代码。
  3. 释放锁,如果等待队列上有等待的线程,从中取一个并唤醒,如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性。

synchronized保护的是对象而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序执行。

因此,一般在保护变量时,需要再所有访问该变量的方法上加上synchd。

2.2 静态方法

synchronized同样适用于静态方法。

public class StaticCounter {
    private static int count = 0;
    public static synchronized void incr() {
        count++;
    }
    public static synchronized int getCount() {
        return count;
    }
}

对于实例方法,保护的是当前实例对象this;

对于静态方法,保护的是类对象,这里是StaticCounter.class。实际上,每个对象都有一个锁和一个等待队列,类对象也不例外。

2.3 代码块

synchronized还可以用于包装代码块。

public class Counter {
    private int count;
   public void incr() {
       synchronized (this) {
           count++;
       }
   }
   public int getCount() {
       synchronized (this) {
           return count;
       }
   }
}

synchronized括号里面的就是保护的对象,对于实例方法,就是this,{}里面是同步执行的代码。对于前面的StaticCounter类,等价的代码如下:

public class StaticCounter {
    private static int count = 0;
    public static void incr() {
        synchronized (StaticCounter.class){
            count++;
        }
    }
    public static int getCount() {
        synchronized (StaticCounter.class) {
            return count;
        }
    }
}

synchronized同步的对象可以是任意对象,任意对象都有一个锁的等待队列,或者说,任何对象都可以作为锁对象。如,Counter类的等价代码也可以如下代码所示:

public class Counter {
    private int count;
    private Object lock = new Object();
   public void incr() {
       synchronized (lock) {
           count++;
       }
   }
   public int getCount() {
       synchronized (lock) {
           return count;
       }
   }
}

3 synchronized的三个特性

  1. 可重入性
  2. 内存可见性
  3. 死锁

可重入是通过记录锁的持有线程和持有数量来实现的。

synchronized除了保证原子操作外,还可以保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。

不过如果只是为了保证内存可见性,使用synchronized的成本比较高,有一个轻量级的方式,就是给变量加修饰符volatile。

死锁

public class DeadLockDemo {
    private static Object lockA = new Object();
    private static Object lockB = new Object();
    private static void startThreadA() {
        Thread aThread = new Thread() {
            @Override
            public void run() {
                synchronized (lockA) {
                    try{
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {

                    }
                    synchronized (lockB) {

                    }
                }
            }
        };
        aThread.start();
    }
    private static void startThreadB() {
        Thread bThread = new Thread() {
            @Override
            public void run() {
                synchronized (lockB) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockA) {

                    }
                }
            }
        };
        bThread.start();
    }

    public static void main(String[] args) {
        startThreadA();
        startThreadB();
    }
}

运行后aThread和bThread陷入了相互等待。

应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。如上面代码,均先申请lockA,再申请lockB。

public class DeadLockDemo {
    private static Object lockA = new Object();
    private static Object lockB = new Object();
    private static void startThreadA() {
        Thread aThread = new Thread() {
            @Override
            public void run() {
                synchronized (lockA) {
                    try{
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {

                    }
                    synchronized (lockB) {

                    }
                }
            }
        };
        aThread.start();
    }
    private static void startThreadB() {
        Thread bThread = new Thread() {
            @Override
            public void run() {
                synchronized (lockA) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockB) {

                    }
                }
            }
        };
        bThread.start();
    }

    public static void main(String[] args) {
        startThreadA();
        startThreadB();
    }
}

迭代异常

对于同步容器,虽然单个操作是安全的,但迭代并不是。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class startModify {
    private static void startModifyThread(final List<String> list) {
        Thread modifyThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    list.add("item " + i);
                    try {
                        Thread.sleep((int) (Math.random()+10));
                    } catch (InterruptedException e) {

                    }
                }
            }
        });
        modifyThread.start();
    }
    private static void startIteratorThread(final List<String> list) {
        Thread iteratorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    for (String str : list) {

                    }
                }
            }
        });
        iteratorThread.start();
    }

    public static void main(String[] args) {
        final List<String> list = Collections.synchronizedList(new ArrayList<String>());
        startIteratorThread(list);
        startModifyThread(list);
    }
}

Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at beforeJob.startModify$2.run(startModify.java:29)
    at java.lang.Thread.run(Thread.java:748)

如果在遍历的同时容器发生了结构性的变化,就会抛出该异常。如果要避免这个异常,需要在遍历的时候给整个容器对象加锁。

4 并发容器

Java中有许多专为并发设计的容器类。

  • CopyOnWriteArrayList
  • ConcurrentHashMap
  • ConcurrentLinkedQueue
  • ConcurrentSkipListSet

5 线程的基本协作机制

协作场景:

  1. 生产者/消费者协作模式
  2. 同时开始
  3. 等待结束
  4. 异步结果
  5. 集合点

 

wait/notify

public class WaitThread extends Thread {
    private volatile boolean fire = false;

    @Override
    public void run() {
        try {
            synchronized (this) {
                while (!fire) {
                    wait();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void fire() {
        this.fire = true;
        notify();
    }

    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        System.out.println("fire");
        waitThread.fire();
    }
}

在设计多线程协作时,需要想清楚协作的共享变量和条件是什么,这是协作的核心。

生产者/消费者模式

生产者/消费者协作队列:

import java.util.ArrayDeque;
import java.util.Queue;

public class MyBlockingQueue<E> {
    private Queue<E> queue = null;
    private int limit;
    public MyBlockingQueue(int limit) {
        this.limit = limit;
        queue = new ArrayDeque<>(limit);
    }
    public synchronized void put(E e) throws InterruptedException {
        while (queue.size() == limit) {
            wait();
        }
        queue.add(e);
        notifyAll();
    }
    public synchronized E take() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        E e = queue.poll();
        notifyAll();
        return e;
    }
}

生产者:

public class Producer extends Thread {
    MyBlockingQueue<String> queue;
    public Producer(MyBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (true) {
                String task = String.valueOf(num);
                queue.put(task);
                System.out.println("producer task " + task);
                num++;
                Thread.sleep((int) (Math.random() * 100));

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

消费者:

public class Consumer extends Thread {
    MyBlockingQueue<String> queue;
    public Consumer(MyBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                String task = queue.take();
                System.out.println("handle task " + task);
                Thread.sleep((int) (Math.random() * 100));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试代码:
 

public class Test {
    public static void main(String[] args) {
        MyBlockingQueue<String> queue = new MyBlockingQueue<>(10);
        new Producer(queue).start();
        new Consumer(queue).start();
    }
}

同时开始

协作对象:

public class FireFlag {
    private volatile boolean fired = false;
    public synchronized void waitForFire() throws InterruptedException {
        while (!fired) {
            wait();
        }
    }
    public synchronized void fire() {
        this.fired = true;
        notifyAll();
    }
}

比赛运动员:

public class Racer extends Thread {
    FireFlag fireFlag;
    public Racer(FireFlag fireFlag) {
        this.fireFlag = fireFlag;
    }

    @Override
    public void run() {
        try {
            this.fireFlag.waitForFire();
            System.out.println("start run " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
        }
    }
}

主程序代码:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        FireFlag fireFlag = new FireFlag();
        Thread[] races = new Thread[num];
        for (int i = 0; i < num; i++) {
            races[i] = new Racer(fireFlag);
            races[i].start();
        }
        Thread.sleep(1000);
        fireFlag.fire();
    }
}

等待结束

协作对象:

public class MyLatch {
    private int count;
    public MyLatch(int count) {
        this.count = count;
    }
    public synchronized void await() throws InterruptedException {
        while (count > 0) {
            wait();
        }
    }
    public synchronized void countDown() {
        count--;
        if (count <= 0) {
            notifyAll();
        }
    }
}

使用MyLatch的工作子线程:

public class Worker extends Thread {
    MyLatch latch;
    public Worker(MyLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep((int)(Math.random()*1000));
            this.latch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

主程序:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        int workedNum = 100;
        MyLatch latch = new MyLatch(workedNum);
        Worker[] workers = new Worker[workedNum];
        for (int i = 0; i < workedNum; i++) {
            workers[i] = new Worker(latch);
            workers[i].start();
        }
        latch.await();
        System.out.println("collect worked result");
    }
}

异步结果

在Java中,表示子任务的接口是Callable,声明为:

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

为表示异步调用的结果,定义一个接口MyFuture,如下:

public interface MyFuture<V> {
    V get() throws Exception;
}

集合点

各个线程先是分头行动,各自到达一个集合点,在集合点需要集齐所有线程,交换数据,然后再进行下一步动作。

协作对象:

public class AssemblePoint {
    private int n;
    public AssemblePoint(int n) {
        this.n = n;
    }
    public synchronized void await() throws InterruptedException {
        if (n > 0) {
            n--;
            if (n == 0) {
                notifyAll();
            } else {
                while (n != 0) {
                    wait();
                }
            }
        }
    }
}

集合点协作:

public class AssemblePointDemo {
    static class Tourist extends Thread {
        AssemblePoint ap;
        public Tourist(AssemblePoint ap) {
            this.ap = ap;
        }

        @Override
        public void run() {
            try {
                Thread.sleep((int) (Math.random()*1000));
                ap.await();
                System.out.println("arrived");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) {
        int num = 10;
        Tourist[] threads = new Tourist[num];
        AssemblePoint ap = new AssemblePoint(num);
        for (int i = 0; i < num; i++) {
            threads[i] = new Tourist(ap);
            threads[i].start();
        }
    }
}

6 线程的中断

在Java中,停止一个线程的主要机制是中断,中断并不是强迫中止 一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。

线程对中断的反应

interrupt()对线程的影响与线程的状态和在进行的IO操作有关。

线程的状态有:

  • RUNNABLE:线程正在运行或具备运行条件只是在等待操作系统调度
  • WAITING/TIMED_WAITING:线程在等待某个条件或超时
  • BLOCKED:线程在等待锁,试图进入同步块
  • NEW/TERMINATED:线程还未启动或已结束

RUNNABLE:如果线程正在运行中,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,没有任何其他作用。

WAITING/TIMED_WAITING:线程调用join/wait/sleep方法进入WAITING或TIMED_WAITING状态,在这些状态时,对线程对象调用interrupt()会使得该线程抛出InterruptException。需要注意的是,抛出异常后,中断标志位会被清空,而不是被设置。

BLOCKED:如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依旧会处于BLOCKED状态。即,interrupt()并不能使一个在等待锁的线程真正“中断”。

NEW/TERMINATE:如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。

二 并发包的基础

  • 原子变量
  • 显式锁
  • 显式条件

1 原子变量和CAS

Java并法包中的基本原子变量有一下几种:

  • AtomicBoolean:原子Boolean类型,常用来在程序中表示一个标志位
  • AtomicInteger:原子Integer类型
  • AtomicLong:原子Long类型,常用来在程序中生成唯一序列号
  • AtomicReference:原子引用类型,用来以原子方式更新复杂类型

1.1 AtomicInteger

之所以称为原子变量,是因为它包含一些以原子方式实现组合操作的方法。

  • public final int getAndSet(int newValue)
  • public final int getAndIncrement()
  • ......

这些方法的实现都依赖另一个public方法:

public final boolean compareAndSet(int expect,int update)

compareAndSet是一个非常重要的方法,比较并设置,简称为CAS。两个参数expect和update,以原子方式实现如下功能:如果当前值等于expect,则更新为update,否则不更新,如果更新成功,返回true,否则返回false。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    private static AtomicInteger counter = new AtomicInteger(0);
    static class Visitor extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            threads[i] = new Visitor();
            threads[i].start();
        }
        for (int i = 0; i < num; i++) {
            threads[i].join();
        }
        System.out.println(counter.get());
    }
}

1000000

1.2 基本原理和思维

AtomicInteger的主要内部成员是:

private volatile int value;

synchronized代表一种阻塞式算法。

原子变量的更新是非阻塞式的。

1.3 实现锁

基于CAS,除了可以实现乐观非阻塞算法之外,还可以实现悲观阻塞式算法,比如锁。

Java并发包中的所有阻塞式工具、容器、算法也都是基于CAS的。用AtomicInteger实现一个锁MyLock。

import java.util.concurrent.atomic.AtomicInteger;

public class MyLock {
    private AtomicInteger status = new AtomicInteger(0);
    public void lock() {
        while (!status.compareAndSet(0,1)) {
            Thread.yield();
        }
    }
    public void unlock() {
        status.compareAndSet(1,0);
    }
}

2 显式锁

Java并发包中的显式锁接口和类位于包java.util.concurrent.locks下,主要接口和类有:

  • 锁接口Lock,主要实现类ReentrantLock
  • 读写锁接口ReadWriteLock,主要实现类是ReentrantReadWriteLock

2.1 接口Lock

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

2.2 可重入锁ReentrantLock

Lock接口的主要实现类是ReentrantLock,它的基本用法lock/unlock实现了与synchronized一样的语义,包括:

  • 可重入,一个线程在持有一个锁的前提下,可以继续获得该锁
  • 可以解决竞态条件问题
  • 可以保证内存可见性

ReentrantLock有两个构造函数:

public ReentrantLock() {
        sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

使用ReentrantLock实现Counter,代码为:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final Lock lock  = new ReentrantLock();
    private volatile int count;
    public void incr() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    public int getCount() {
        return count;
    }
}

使用tryLock(),可以避免死锁。

ReentrantLock内部使用AQS,有三个内部类:

  • abstract static class Sync extends AbstractQueuedSynchronizer
  • static final class NonfairSync extends Sync
  • static final class FairSync extends Sync

Sync是抽象类,NonfairSync是fair为false时使用的类,FairSync是fire为true是使用的类。

对比ReentrantLock和synchronized

  • 相比synchronized,ReentrantLock可以实现与synchronized相同的语义,而且支持以非阻塞方式获取锁,可以相应中断,可以限时,更为灵活。
  • 不过,synchronized的使用更为简单,写的代码更少,也更不容易出错。

synchronized代表一种声明式编程思维,程序员更多的是表达一种同步声明,有Java系统负责具体实现,程序员不知道其实现细节;

显式锁代表一种命令式编程思维,程序员实现所有细节。

3 显式条件

显式锁与synchronized相对应,而显式条件与wait/notify相对应。

wait/notify与synchronized配合使用,显式条件与显式锁配合使用。条件与锁相关联,创建条件变量需要通过显式锁。Condition表示条件变量,是一个接口。

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

使用显式条件进行协作:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class WaitThread extends Thread {
    private volatile boolean fire = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    @Override
    public void run() {
        try {
            lock.lock();
            try {
                while (!fire) {
                    condition.await();
                }
            } finally {
                lock.unlock();
            }
            System.out.println("fired");
        } catch (InterruptedException e) {
            Thread.interrupted();
        }
    }
    public void fire() {
        lock.lock();
        try {
            this.fire = true;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        System.out.println("fire");
        waitThread.fire();
    }
}

生产者/消费者模式

使用显式锁/条件实现的阻塞队列

三 并发容器

  • 写时复制的List和Set
  • ConcurrentHashMap
  • 基于SkipList的Map和Set
  • 各种并发队列

1 写时复制的List和Set

  • CopyOnWriteArrayList
  • CopyOnWriteArraySet

CopyOnWriteArraySet内部是通过CopyOnWriteList实现的,其声明为:

private final CopyOnWriteArrayList<E> al;

两者适用于读远多于写、集合不太大的场合,它们采用了写时复制,这是计算机程序中一种重要的思维和技术。

2 ConcurrentHashMap

是一个常用的并发容器,是HashMap的并发版本,与HashMap相比,有如下特点:

  • 并发安全
  • 直接支持一些原子复合操作
  • 支持高并发,读操作完全并行,写操作支持一定的并行
  • 与同步容器Collection.synchronizedMap相比,迭代不用加锁,不会抛出ConcurrentModificationException
  • 弱一致性

高并发的基本机制

在Java7中,主要有两点:

  • 分段锁
  • 读不需要锁

同步容器使用synchronized,所有方法竞争同一个锁;而ConcurrentHashMap采用分段锁技术,将数据分为多个段,而每个段有一个独立的锁,而每个段有一个独立的锁,每一个段相当于独立的哈希表,分段的依据也是哈希表值,无论是保存键值对还是根据键查找,都会先根据键的哈希值映射到段,再在段对应的哈希表上进行操作。

3 基于跳表的Map和Set

Java并发包中与TreeMap和TreeSet对应的并发版本是ConcurrentSkipListMap和ConcurrentSkipListSet。

4 并发队列

Java并发包中提供了丰富的队列类。

  • 无锁非阻塞并发队列:ConcurrentLinkedQueue和ConcurrentLinkedDeque
  • 普通阻塞队列:基于数组的ArrayBlockingQueue,基于链表的LinkedBlockingQueue和LinkedBlockingDeque
  • 优先级阻塞队列:PriorityBlockingQueue
  • 延时阻塞队列:DelayQueue
  • 其他阻塞队列:SynchronousQueue和LinkedTransferQueue

四 异步任务执行服务

在之前的介绍中,线程Thread既表示要执行的任务,又表示执行的机制。Java并发包提供了一套框架,大大简化了执行异步任务所需的开发。这套框架引入了一个“执行任务”的概念,它将“任务的提交”和“任务的执行”相分离,“执行任务”封装了任务执行的细节,对于任务提交者而言,它可以关注于任务本身,如提交任务、获取结果、取消任务,而不需要关注任务执行的细节,如线程创建、任务调度、线程关闭等。

1 基本概念和原理

异步任务执行服务的基本接口、用法和实现原理。

基本接口:

  • Runnable和Callable:表示要执行的异步任务
  • Executor和ExecutorService:表示要执行服务
  • Future:表示异步任务的结果

固定延时:

import java.util.Timer;
import java.util.TimerTask;

public class TimerFixedDelay {
    static class LongRunningTask extends TimerTask {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("long running finished");
        }
    }
    static class FixedDelayTask extends TimerTask {
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new LongRunningTask(),10);
        timer.schedule(new FixedDelayTask(),100,1000);
    }
}

五 同步和协作工具类

Java并发包中有一些专门的同步和协作的工具类,包括:

  • 读写锁 ReentrantReadWriteLock
  • 信号量 Semaphore
  • 倒计时门栓 CountDownLatch
  • 循环栅栏 CyclicBarrier

信号量类Semaphore限制对资源的并发访问数,有两个构造方法:

  • public Semaphore(int permits)
  • public Semaphore(int permits, boolean fair)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    static class Tourist extends Thread {
        CyclicBarrier barrier;
        public Tourist(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                Thread.sleep((int) (Math.random()*1000));
                barrier.await();
                System.out.println(this.getName() + " arrived A " + System.currentTimeMillis());
                Thread.sleep((int) (Math.random()*1000));
                barrier.await();
                System.out.println(this.getName() + " arrived B " + System.currentTimeMillis());
            } catch (InterruptedException e) {
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int num = 3;
        Tourist[] threads = new Tourist[num];
        CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() {
            @Override
            public void run() {
                System.out.println("all arrived " + System.currentTimeMillis() + " executed by " + Thread.currentThread().getName());
            }
        });
        for (int i = 0; i < num; i++) {
            threads[i] = new Tourist(barrier);
            threads[i].start();
        }
    }
}

all arrived 1579507780205 executed by Thread-1
Thread-1 arrived A 1579507780205
Thread-2 arrived A 1579507780205
Thread-0 arrived A 1579507780205
all arrived 1579507781163 executed by Thread-2
Thread-2 arrived B 1579507781163
Thread-1 arrived B 1579507781163
Thread-0 arrived B 1579507781163

理解ThreadLocal

public class Test {
    static ThreadLocal<Integer> local = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        Thread child = new Thread() {
            @Override
            public void run() {
                System.out.println("child thread initial: " + local.get());
                local.set(200);
                System.out.println("child thread final: " + local.get());
            }
        };
        local.set(100);
        child.start();
        child.join();
        System.out.println("main thread final: " + local.get());
    }
}

child thread initial: null
child thread final: 200
main thread final: 100

说明,main线程对local变量的设置对child线程不起作用,child线程对local变量的改变也不会影响main线程。它们访问的虽然是同一个变量local,但每个线程都有自己的独立的值,这就是线程本地变量的含义。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值