Java并发编程,互斥同步和线程之间的协做

互斥同步和线程之间的协做

互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另外一个是 JDK 实现的 ReentrantLock。java

synchronized

1. 同步一个代码块bash

public void func() {
    synchronized (this) {
        // ...
    }
}复制代码

它只做用于同一个对象,若是调用两个对象上的同步代码块,就不会进行同步。ide

对于如下代码,使用 ExecutorService 执行了两个线程,因为调用的是同一个对象的同步代码块,所以这两个线程会进行同步,当一个线程进入同步语句块时,另外一个线程就必须等待。性能

public class SynchronizedExample {

    public void func1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
    }
}
public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func1());
    executorService.execute(() -> e1.func1());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9复制代码

对于如下代码,两个线程调用了不一样对象的同步代码块,所以这两个线程就不须要同步。从输出结果能够看出,两个线程交叉执行。优化

public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    SynchronizedExample e2 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func1());
    executorService.execute(() -> e2.func1());
}
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9复制代码

2. 同步一个方法ui

public synchronized void func () {
    // ...
}复制代码

它和同步代码块同样,做用于同一个对象。this

3. 同步一个类spa

public void func() {
    synchronized (SynchronizedExample.class) {
        // ...
    }
}复制代码

做用于整个类,也就是说两个线程调用同一个类的不一样对象上的这种同步语句,也会进行同步。线程

public class SynchronizedExample {

    public void func2() {
        synchronized (SynchronizedExample.class) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
    }
}
public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    SynchronizedExample e2 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func2());
    executorService.execute(() -> e2.func2());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9复制代码

4. 同步一个静态方法code

public synchronized static void fun() {
    // ...
}复制代码

做用于整个类。

ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

public class LockExample {

    private Lock lock = new ReentrantLock();

    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 确保释放锁,从而避免发生死锁。
        }
    }
}
public static void main(String[] args) {
    LockExample lockExample = new LockExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> lockExample.func());
    executorService.execute(() -> lockExample.func());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9复制代码

比较

1. 锁的实现

synchronized 是关键字,由 JVM 实现的,而 ReentrantLock 是类,由 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了不少优化,例如自旋锁等,synchronized 与 ReentrantLock 大体相同。

3. 等待可中断

ReentrantLock 能够获取锁的等待时间并能够进行设置,这样避免了死锁。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次得到锁。

synchronized 中的锁是非公平的,ReentrantLock 默认状况下也是非公平的,可是也能够是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 能够同时绑定多个 Condition 对象,灵活地实现多路通知。

6. 机制

synchronized 操做Mark World,ReentrantLock 调用Unsafe类的park()方法

使用选择

除非须要使用 ReentrantLock 的高级功能,不然优先使用 synchronized。这是由于 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是全部的 JDK 版本都支持。而且使用 synchronized 不用担忧没有释放锁而致使死锁问题,由于 JVM 会确保锁的释放。

线程之间的协做

当多个线程能够一块儿工做去解决某个问题时,若是某些部分必须在其它部分以前完成,那么就须要对线程进行协调。

join()

在线程中调用另外一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

对于如下代码,虽然 b 线程先启动,可是由于在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,所以最后可以保证 a 线程的输出先于 b 线程的输出。

public class JoinExample {

    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }

    private class B extends Thread {

        private A a;

        B(A a) {
            this.a = a;
        }

        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }

    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
}
public static void main(String[] args) {
    JoinExample example = new JoinExample();
    example.test();
}
A
B复制代码

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件知足,线程在等待时会被挂起,当其余线程的运行使得这个条件知足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,不然会在运行时抛出 IllegalMonitorStateException。

使用 wait() 挂起期间,线程会释放锁。这是由于,若是没有释放锁,那么其它线程就没法进入对象的同步方法或者同步控制块中,那么就没法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,形成死锁。

public class WaitNotifyExample {

    public synchronized void before() {
        System.out.println("before");
        notifyAll();
    }

    public synchronized void after() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    WaitNotifyExample example = new WaitNotifyExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after复制代码

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,能够在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。

相比于 wait() 这种等待方式,await() 能够指定等待的条件,所以更加灵活。

使用 Lock 来获取一个 Condition 对象。

public class AwaitSignalExample {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void before() {
        lock.lock();
        try {
            System.out.println("before");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    AwaitSignalExample example = new AwaitSignalExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after复制代码
相关文章
相关标签/搜索