并发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);
    }
}
View Code

  大部分情况下,会输出期望的值: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);
    }
}
View Code

  预期结果是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");
    }
}
View Code

  预期是两个线程都退出,但实际执行时,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)
View Code

  他们是给所有容器方法多加上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);}
}
View Code

  同步容器的性能是比较低的,当并发访问量大的时候性能比较差,Java中还有很多专为并发设计的容器类:CopyOnWriteArrayList、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListSet等。

猜你喜欢

转载自www.cnblogs.com/wange/p/10990233.html
01-