并发01-基础
Posted wange
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发01-基础相关的知识,希望对你有一定的参考价值。
一、共享内存
线程之间可以共享内存,他们可以访问和操作相同的对象,比如:
public class ShareMemoryDemo private static int shared = 0; private static void increShared() shared++; static class ChildThread extends Thread List<String> list; public ChildThread(List<String> list) this.list = list; @Override public void run() increShared(); list.add(Thread.currentThread().getName()); public static void main(String[] args) throws InterruptedException List<String> list = new ArrayList<>(); Thread thread1 = new ChildThread(list); Thread thread2 = new ChildThread(list); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(shared); System.out.println(list);
大部分情况下,会输出期望的值:2 [Thread-0, Thread-1],当多个线程操作相同的变量时,可能会出现一些意料之外的结果,包括竟态条件和内存可见性。
1、竟态条件(多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,结果可能正确也可能不正确)
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);
预期结果是100万,但实际执行,每次结果都不一样,经常是99万多,因为counter++不是原子操作,可以使用synchronized关键字、显式锁、原子变量等方法解决。
2、内存可见性(某个线程对共享变量的修改,另一个线程不一定马上就能看到,甚至永远看不到)
public class VisibilityDemo private static boolean shutdown = false; static class HelloThread extends Thread @Override public void run() while (!shutdown) //... 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。可以使用volatile关键字、synchronized关键字、显式锁等方法解决。
二、synchronized
可以用来修饰实例方法、静态方法、代码块。
实例方法:保护的是同一个对象的调用,确保同时只能有一个线程执行。
静态方法:保护的是类对象。
代码块:同步对象可以是任意对象,任何对象都可以作为锁对象。
synchronized是可重入的,对于同一个线程,它获得锁之后,在调用其它需要同样锁的代码时,可以直接调用。可重入是通过记录锁的持有数量来实现的。
内存可见性:对于本来就是原子的操作方法,可以使用轻量级的volatile关键字保证可见性。
三、同步容器
类Collection中有一些方法,可以返回线程安全的同步容器
public static <T> List<T> synchronizedList(List<T> list) public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
他们是给所有容器方法多加上synchronized来实现安全的,比如 SynchronizedCollection中的方法:
public int size() synchronized (mutex) return c.size(); public boolean isEmpty() synchronized (mutex) return c.isEmpty(); public boolean contains(Object o) synchronized (mutex) return c.contains(o);
同步容器的性能是比较低的,当并发访问量大的时候性能比较差,Java中还有很多专为并发设计的容器类:CopyOnWriteArrayList、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListSet等。
以上是关于并发01-基础的主要内容,如果未能解决你的问题,请参考以下文章
Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题