java线程协作_Java并发编程,互斥同步和线程之间的协作

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

互斥同步

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

synchronized

同步一个代码块

public void func() {

synchronized (this) {

// ...

}

}

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

对于以下代码,使用 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

同步一个方法

public synchronized void func () {

// ...

}

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

同步一个类

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

同步一个静态方法

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

比较

锁的实现

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

性能

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

等待可中断

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

公平锁

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

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

锁绑定多个条件

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

机制

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

免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。

传送门:https://mp.weixin.qq.com/s/Jz...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值