synchronized:同步
对象锁:一个对象一把锁,用于实例方法,锁住对象中的所有同步实例方法
synchronized直接加在方法上和synchronized(this)都是对当前对象加锁。
重入锁:同步方法A中执行方法B,可以直接执行,无需抢锁,包括父子的情况。
类锁:一个类一把锁,用于静态方法,锁住类中的所有同步静态方法。
对象锁和类锁不冲突,持有类锁的不会影响同步的实例方法。
锁改变:其他线程已经在队列中抢lock这把锁了,那改变了lock也还是会等待。
其他线程没有在队列中抢lock,而是在lock改变后才来的,那就不会等lock这把锁,而是changelock这把锁。
锁对象内的属性改变不影响锁,锁对象被new才会影响。
死锁:线程交叉持有对方需要的锁,导致线程都在等其他线程释放锁。
private String lock1 = "lock1"; private String lock2 = "lock2"; public void execute1() { synchronized (lock1) { System.out.println("execute1 lock1---"); synchronized (lock2) { System.out.println("execute1 lock2---"); } } } public void execute2() { synchronized (lock2) { System.out.println("execute2 lock2---"); synchronized (lock1) { System.out.println("execute2 lock1---"); } } }
原子类:
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
i++:非原子性,拆成了几个步骤,并发时会有问题
Atomic类:用了CAS算法,保证了原子性。
CAS: 是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”。
简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。但是多次调用之间的原子性还得同步。
public synchronized int add(){ try { count.addAndGet(1); Thread.sleep(10); count.addAndGet(2); Thread.sleep(10); count.addAndGet(3); Thread.sleep(10); } catch (Exception e) { // TODO: handle exception } return count.addAndGet(4); }
volatile:当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
ThreadLocal:在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量。
threadlocal结构:一个线程有一个ThreadLocalMap,map中可以存放多个threadlocal,map的key是threadlocal,值是需要设置的value,每个线程中可有多个threadLocal变量。
使用场景:如数据库连接,hibernate就用到了这个。还有如写接口时每次请求(每个线程)都要根据当前请求保存当前请求的一些数据。
单例模式:利用classLoader的比较精简的方式:
public class Singleton { private Singleton(){} static class SingletonClass{ static Singleton singleton = new Singleton(); } public static Singleton getInstance(){ return SingletonClass.singleton; } public static void main(String[] args) { System.out.println(Singleton.getInstance()); System.out.println(Singleton.getInstance()); System.out.println(Singleton.getInstance()); } }
同步容器,并发容器
同步容器:可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。
如:Vector HashTable Collections.synchronized
缺点:1、 通过同步方法将访问操作串行化,导致并发环境下效率低下。
2、 复合操作(迭代、条件运算如没有则添加等)非线程安全,需要客户端代码来实现加锁。
并发容器:使用于高并发时。分为阻塞队列和非阻塞队列,都是线程安全的队列。
阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。
JDK7提供了7个阻塞队列。用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
LinkedBlockingQueue中的锁是分离的,生产者的锁PutLock,消费者的锁takeLock。而ArrayBlockingQueue生产者和消费者使用的是同一把锁。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
public static void main(String[] args) throws Exception { // 非阻塞队列,通过循环CAS算法实现 // ConcurrentLinkedQueue 基于链接节点的无界线程安全队列 ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); queue.add("aaa"); /** * 阻塞队列:通过加锁实现 *阻塞队列和普通队列最大不同:阻塞队列有阻塞添加和阻塞删除方法 *阻塞添加:当阻塞队列已满时,队列会阻塞,直到队列可以添加 * 添加方法:add:添加成功返回true,失败抛出异常 * offer:成功返回true,如果队列已满,则返回false * put:将元素插入队列的尾部,如果队列已满,则一直阻塞 *阻塞删除:当阻塞队列为空时,队列会阻塞,直到队列有元素可以删除 * 删除方法:remove:移除指定元素,成功返回true,失败返回false * poll:获取并移除此队列的头元素,若队列为空,返回null * take:获取并移除队列的头元素,若没有元素则一直阻塞 */ // 数组实现的有界阻塞队列,必须指定队列大小 // take和put方法是同一把锁 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(4); arrayBlockingQueue.put("aaa"); arrayBlockingQueue.put("ddd"); arrayBlockingQueue.put("bbb"); arrayBlockingQueue.put("ccc"); // arrayBlockingQueue.put("hhh");// 队列已满,会阻塞在此 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); // arrayBlockingQueue.take();// 此时队列为空,会阻塞在此 // 链表实现的有界队列阻塞队列,默认值为Integer.MAX_VALUE // take和put方法是两把不同的锁 LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>(); linkedBlockingQueue.put("aaa"); linkedBlockingQueue.put("ddd"); linkedBlockingQueue.put("bbb"); linkedBlockingQueue.put("ccc"); // arrayBlockingQueue.put("hhh");// 队列已满,会阻塞在此 System.out.println(linkedBlockingQueue.take()); System.out.println(linkedBlockingQueue.take()); System.out.println(linkedBlockingQueue.take()); System.out.println(linkedBlockingQueue.take()); // arrayBlockingQueue.take();// 此时队列为空,会阻塞在此 }
非阻塞队列:使用循环CAS的方式来实现。
ConcurrentLinkedQueue:基于链接节点的无界线程安全队列
ConcurrentHashMap:默认将hash表分为16个桶,每个桶一把锁。1.8有些变化,用了CAS算法,有更进一步的改进。
CopyOnWrite类:cow是计算机中通用的一种策略,读写分离的思想,读和写不同的容器。修改的时候,copy一个副本出去改,改完再将之前的引用指向修改后的。适用于读多写少的并发场景。注意:只保证了最终一致性。改动的不会立即生效。
如CopyOnWriteArrayList:add的时候,有加锁,只copy了一个副本出来,不然copy多个出来 改完后指向会出问题。读的时候就无所谓了。
ThreadPoolExecutor:
public static void main(String[] args) { BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 1, // 核心线程数 5, // 最大线程数 60, // 线程空闲多长时间被销毁 TimeUnit.SECONDS, // 空闲时间单位 queue, // 指定队列 new MyRejected() ); for (int i = 1; i <= 11; i++) { final int j = i; poolExecutor.execute(new Runnable() { @Override public void run() { System.out.println(j + ""); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } /** * 输出:1 8 7 9 10 拒绝策略 2 5 4 3 6 * 分析:核心线程为1,最大线程为5,队列长度为5, * 第1个runnable:因为核心线程数为1,一进来就执行了 * 第2-6个runnable:因为队列长度为5,所以被放到了队列中 * 第7-11个runnable:因为最大线程数为5,7-11有5个包括核心的1个,共有6个,所以执行了前5个,最后一个被拒绝了 * 所以是先执行第一个,然后7 8 9 10,第11个走了拒绝策略,最后是2 3 4 5 6 */ } static class MyRejected implements RejectedExecutionHandler{ public MyRejected() { } @Override public void rejectedExecution(Runnable runnable, ThreadPoolExecutor paramThreadPoolExecutor) { System.out.println("拒绝策略:" + runnable.toString()); } }
CountDownLatch:类似计数器的功能。比如有一个任务1,它要等待其他2个任务执行完毕之后才能执行。
public static void main(String[] args) { /** * 可以想象为有三个人,其中一个人已经在100米处了,其他两个人还在起点,在100米处的人需要等其他两个人到了100米的地方才可以往前走。 */ CountDownLatch latch = new CountDownLatch(2); ExecutorService threadPool = Executors.newFixedThreadPool(3); for (int i = 1; i <= 2; i++) { final int j = i; threadPool.execute(new Thread("t" + i) { public void run() { System.out.println("线程:" + Thread.currentThread().getName() + " 执行"); try { Thread.sleep(j * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成"); latch.countDown(); }; }); } System.out.println("线程:" + Thread.currentThread().getName() + " 执行"); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成"); threadPool.shutdown(); }
CyclicBarrier:回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier可以被重用。
public static void main(String[] args) { /** * 可以想象为3个人走路,走的速度都不一样,走到一定的程度时,比如100米时,有3个栅栏拦着,需要这三个人每人拿掉一把,然后才可以一起走 */ CyclicBarrier barrier = new CyclicBarrier(3);// 3即代表着有3个人一起走路 ExecutorService threadPool = Executors.newFixedThreadPool(3); for (int i = 1; i <= 3; i++) { final int j = i; threadPool.execute(new Thread("t" + i){ @Override public void run() { System.out.println("线程:" + Thread.currentThread().getName() + " 开始执行"); try { Thread.sleep(j * 1000);// 代表走的速度都不一样 System.out.println("线程:" + Thread.currentThread().getName() + " 开始等待其他线程执行完"); barrier.await();// 代表有栅栏拦在这,需要三个人都执行await方法才可以继续往前走 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成"); } }); } threadPool.shutdown(); }
Semaphore:信号量,可以控制同时访问的线程个数。
public static void main(String[] args) { ExecutorService threadPool = Executors.newCachedThreadPool(); // 只允许5个线程同时访问 Semaphore semaphore = new Semaphore(5); for (int i = 1; i <= 20; i++) { final int j = i; threadPool.execute(new Thread("t" + j) { @Override public void run() { try { semaphore.acquire();// 获取许可 System.out.println("线程:" + Thread.currentThread().getName() + " 开始执行"); Thread.sleep(2000); System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成"); semaphore.release();// 释放 } catch (InterruptedException e) { e.printStackTrace(); } } }); } threadPool.shutdown(); }CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于 某个线程A等待若干个其他线程执行完任务之后,它才执行;
CyclicBarrier一般用于 一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
Future:异步获取多线程的执行结果。可以实现:1.启动多线程任务 2.处理其他事 3.收集多线程任务结果。
public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newFixedThreadPool(3); // 1、启动多线程任务 Future<String> future = threadPool.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("开始执行---"); Thread.sleep(3000); System.out.println("执行完毕---"); return "执行结果---"; } }); // 2、处理其他事 Thread.sleep(2000); // future还没执行完时会阻塞在此处 // 3、收集多线程任务结果 System.out.println(future.get()); threadPool.shutdown(); }