Java并发编程的基础

2020/11/29 posted in  Java并发编程的艺术

##线程间通信

volatile

多线程访问 volatile 修饰的变量都需要从共享内存中获取,而对它的修改必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

synchronized

可以修饰方法或者以同步代码块的形式来使用,确保多个线程在同一时间只能有一个线程处于方法或者代码块中,保证了线程对变量访问的可见性和排他性。

同步代码块的底层实现是 monitorenter 和 monitorexit 指令,而同步方法则是一开方法修饰符上的 ACC_SYNCHRONIZED 完成。

等待/通知机制

生产者、消费者模型

wait()/notify()

wait和notify方法都是锁对象来调用。它们必须在被synchronized修饰的方法内。

wait方法会使在临界区内的线程进入等待状态,同时释放被同步对象的锁。

notify方法会唤醒一个因调用了wait方法而处于阻塞状态的线程,使其进入就绪状态。
被重新唤醒的线程会试图重新获取锁的控制权,并继续执行wait之后的代码。

static class Provider implements Runnable {

	@SneakyThrows
	@Override
	public void run() {
		synchronized (lock) {
			System.out.println("生产者运行开始,当前时间 = " + System.currentTimeMillis());
			lock.wait();
			System.out.println("生产者运行结束,当前时间 = " + System.currentTimeMillis());
		}
	}
}

static class Consumer implements Runnable {

	@SneakyThrows
	@Override
	public void run() {

		synchronized (lock) {
			System.out.println("消费者运行开始,当前时间 = " + System.currentTimeMillis());
			lock.notify();
			System.out.println("消费者运行结束,当前时间 = " + System.currentTimeMillis());
		}
	}
}

其运行结果如下:

生产者运行开始,当前时间 = 1609770771102
消费者运行开始,当前时间 = 1609770771103
消费者运行结束,当前时间 = 1609770771103
生产者运行结束,当前时间 = 1609770771103

wait方法执行后,线程会立即释放锁,而notify方法执行后,线程并不释放锁。

static class Provider implements Runnable {

	@SneakyThrows
	@Override
	public void run() {
		synchronized (lock) {
			System.out.println("生产者运行开始,当前时间 = " + System.currentTimeMillis());
			lock.wait();
			TimeUnit.MILLISECONDS.sleep(300);
			System.out.println("生产者运行结束,当前时间 = " + System.currentTimeMillis());
		}
	}
}

static class Consumer implements Runnable {

	@SneakyThrows
	@Override
	public void run() {

		synchronized (lock) {
			System.out.println("消费者运行开始,当前时间 = " + System.currentTimeMillis());
			lock.notify();
			TimeUnit.MILLISECONDS.sleep(300);
			System.out.println("消费者运行结束,当前时间 = " + System.currentTimeMillis());
		}
	}
}

其运行结果:

生产者运行开始,当前时间 = 1609771313436
消费者运行开始,当前时间 = 1609771313437
消费者运行结束,当前时间 = 1609771313738
生产者运行结束,当前时间 = 1609771314039

从结果上可以证明上述观点。
生产者wait之后,消费者立即开始执行,然而消费者notify后,还要等待sleep时间过后,生产者才重新拿到锁对象。


场景例子:
生产者生产数字到List中,当List中的元素个数为10的时候,发送通知给到消费者,消费者消费List中的元素,使其个数为0.

public class ProviderConsumer {

	static int x = 0;
	private static List<Integer> list = new ArrayList<>();
	private static Object lock = new Object();

	public static void main(String[] args) {
		Thread a = new Thread(new Provider());
		Thread b = new Thread(new Consumer());

		a.start();
		b.start();
	}

	static class Provider implements Runnable {

		@SneakyThrows
		@Override
		public void run() {
			synchronized (lock) {
				while (true) {
					TimeUnit.MILLISECONDS.sleep(100);
					list.add(x++);
					if (list.size() == 10) {
						lock.wait();
					}
					lock.notify();
				}
			}
		}
	}

	static class Consumer implements Runnable {

		@SneakyThrows
		@Override
		public void run() {

			synchronized (lock) {
				while (true) {
					if (list.size() == 10) {
						System.out.println(list);
						list.clear();
						lock.notify();
					}
					lock.wait();
				}
			}
		}
	}
}

join

主线程等待子线程执行完成后在接着执行。
join的作用就是等待子线程对象销毁。

static class MyThread implements Runnable{

	@SneakyThrows
	@Override
	public void run() {
		TimeUnit.SECONDS.sleep(3);
		System.out.println("子线程运行结束");
	}
}
@SneakyThrows
public static void main(String[] args) {
	MyThread t = new MyThread();

	Thread a = new Thread(t);
	a.start();
	a.join();
	System.out.println("主线程运行结束");
}

join使得当前线程等待子线程执行完成之后继续执行当前线程的代码。

join内部使用了wait,所以join方法会释放锁。

ThreadLocal

ThreadLocal即线程变量,属于线程私有。多个线程之间是隔离的。
是一个以ThreadLocal对象为key,任意对象为value的键值对存储结构,绑定在Thread上。

Thread 中的变量
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal set 方法

public void set(T value) {
    Thread t = Thread.currentThread();
    // Map 是与当前线程绑定的
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}


void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}



// ThreadLocal 内部类 ThreadLocalMap 
static class ThreadLocalMap {
    // ThreadLocalMap 内部类 Entry,用的是 弱引用,GC 的时候就会被回收
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

Entry 是 弱引用,发生 GC 就会被回收。

ThreadLocal 本身并不存储,是当做 key 传给内部的 ThreadLocalMap


https://zhuanlan.zhihu.com/p/138689342