JUC并发编程学习笔记 (4)

synchronized锁的对象是方法的调用者,即对象本身,当两个同步方法被调用时,谁先拿到锁先执行。

代码示例 public class OrderTest { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sendSMS(); }).start(); new Thread(() -> { phone.call(); }).start(); } } class Phone { public synchronized void call() { System.out.println("call"); } public synchronized void sendSMS() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendSMS"); } /** * 输出:等待3秒后才输出,虽然同时开启了2个线程调用不同方法,但是因为都是调用了phone对象的同步方法, * 争抢同一把锁,会有执行的先后顺序 * sendSMS * call */ }

同步方法与普通方法被同时调用,普通方法是不会被同步方法锁阻塞的,会直接执行。

两个对象,两个同步方法被同时调用时,对应两把锁,不会互相争抢锁,各自直接执行。

被static修饰的静态同步方法,锁的是Class,跟创建的对象无关,只有Class这一把锁。

一个是被static修饰的静态同步方法,一个是普通同步方法,因为两个的锁不一样,不会争抢锁,static同步方法锁的是Class,而普通同步方法锁的是调用者对象。

线程安全集合类 CopyOnWriteArrayList public class ListTest { public static void main(String[] args) { //多线程下ArrayList边遍历边修改增加会报ConcurrentModificationException // List<String> list = new ArrayList<>(); // Vector 是同步集合但效率不高 // List<String> list = new Vector<>(); // 集合工具类同步 // List<String> list = Collections.synchronizedList(new ArrayList<>()); //JUC的读写分离集合类,写入时复制 List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,2)); System.out.println(list); }).start(); } } } CopyOnWriteArraySet public class SetTest { public static void main(String[] args) { /** * HashSet本质上是HashMap * 利于了HashMap的KEY不重复的功能实现元素唯一性,值是固定的PRESENT * private static final Object PRESENT = new Object(); */ //多线程下HashSet边遍历边修改增加会报ConcurrentModificationException // Set<String> set = new HashSet<>(); // 集合工具类同步 // Set<String> set = Collections.synchronizedSet(new HashSet<>()); //JUC的读写分离集合类,写入时复制 Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 0; i < 10; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0, 2)); System.out.println(set); }).start(); } } } ConcurrentHashMap

原理

BlockingQueue

阻塞队列,指的是当生产者往队列写入生产或者消费者从队列中获取消费时,如果发现队列是满的或者是空的,则写入或者取出的操作将阻塞着,直到有队列有空间继续写入或者有元素可以取出消费。

写入:如果队列满了,就必须阻塞等待消费

取出:如果队列是空的,就必须阻塞等待生产

阻塞队列使用场景:多线程并发处理,线程池

四组写入取出API

抛出异常

不会抛出异常,有返回值

阻塞等待

超时等待

方式 抛出异常 不会抛出异常,有返回值 阻塞等待 超时等待
添加   add()   offer()   put()   offer(E e, long t, TimeUnit t)  
移除   remove()   poll()   take()   poll(long timeout, TimeUnit unit)  
检查队首元素   element()   peek()   -   -  
代码示例 public class BlockingQueueTest { public static void main(String[] args) throws InterruptedException { test4(); } /** * 抛出异常API */ public static void test1(){ ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3); //写入成功返回true,内部其实是调用offer(e)方法 System.out.println(queue.add("one")); System.out.println(queue.add("two")); System.out.println(queue.add("three")); //IllegalStateException: Queue full //当队列满时继续写入会抛IllegalStateException: Queue full 异常 // System.out.println(queue.add("four")); //检查队首元素,有值返回元素但不移除元素,内部其实是调用 peek()方法 //如果队列为空检查不到队首元素则抛出NoSuchElementException 异常 System.out.println(queue.element()); System.out.println("--------------------------------------"); //取出成功返回元素,内部其实是调用poll()方法 System.out.println(queue.remove()); System.out.println(queue.remove()); System.out.println(queue.remove()); //NoSuchElementException //当队列为空时继续取出会抛NoSuchElementException 异常 // System.out.println(queue.remove()); } /** * 输出: * true * true * true * one * -------------------------------------- * one * two * three */ /** * 不会抛出异常,有返回值 API */ public static void test2(){ ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3); //写入成功返回true System.out.println(queue.offer("one")); System.out.println(queue.offer("two")); System.out.println(queue.offer("three")); //当队列满时继续写入会返回false System.out.println(queue.offer("four")); //检查队首元素,有值返回元素但不移除元素 //如果队列为空检查不到队首元素则返回null 空 System.out.println(queue.peek()); System.out.println("--------------------------------------"); //取出成功返回元素 System.out.println(queue.poll()); System.out.println(queue.poll()); System.out.println(queue.poll()); //当队列为空时继续取出会则返回null 空 System.out.println(queue.poll());//null System.out.println(queue.peek());//null } /** * 输出: * true * true * true * false * one * -------------------------------------- * one * two * three * null * null */ /** * 阻塞等待 API ,因为阻塞等待所以不需要检查队首元素 */ public static void test3() throws InterruptedException { ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3); //写入成功返回true queue.put("one"); queue.put("two"); queue.put("three"); //当队列满时会阻塞等待,直到队列有空闲空间可以继续写入 // queue.put("four"); System.out.println("--------------------------------------"); //取出成功返回元素 System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.take()); //当队列为空时继续取出会阻塞等待,直到队列有元素时继续取出 // System.out.println(queue.take()); } /** * -------------------------------------- * one * two * three */ /** * 超时等待 API */ public static void test4() throws InterruptedException { ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3); //写入成功返回true System.out.println(queue.offer("one",2, TimeUnit.SECONDS)); System.out.println(queue.offer("two",2, TimeUnit.SECONDS)); System.out.println(queue.offer("three",2, TimeUnit.SECONDS)); //当队列满时继续写入会返回进行超时等待,如果在指定时间后写入不成功返回false System.out.println(queue.offer("four",2, TimeUnit.SECONDS)); System.out.println("--------------------------------------"); //取出成功返回元素 System.out.println(queue.poll(2, TimeUnit.SECONDS)); System.out.println(queue.poll(2, TimeUnit.SECONDS)); System.out.println(queue.poll(2, TimeUnit.SECONDS)); //当队列为空时继续取出会进行超时等待,如果在指定时间后取出不成功返回null System.out.println(queue.poll(2, TimeUnit.SECONDS));//null } /**输出: * true * true * true * false * -------------------------------------- * one * two * three * null */ } SynchronousQueue

SynchronousQueue不存储元素,put写入一个元素后,必须从里面先take取出元素,否则无法再继续put写入元素。

//示例不好待补充

常用辅助类 CountDownLatch

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxsxy.html