Java同步关键字,同步方法和块

时间:2020-02-23 14:36:56  来源:igfitidea点击:

Java同步关键字在多线程中用于创建一个代码块,该代码块一次只能由一个线程执行。

为什么需要同步?

当我们有多个线程在一个共享实体上工作时,最终结果可能会损坏。
假设我们有一个简单的程序来增加对象的计数器变量。
此变量在所有线程之间共享。

package com.theitroad.threads;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class CounterThread implements Runnable {

	private int count;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	@Override
	public void run() {
		Random rand = new Random();
		try {
			Thread.sleep(rand.nextInt(1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		count++;
	}

	public static void main(String[] args) throws InterruptedException {
		CounterThread ct = new CounterThread();

		List<Thread> threads = new ArrayList<>();
		for (int i = 0; i < 100; i++) {
			Thread t = new Thread(ct);
			threads.add(t);
			t.start();
		}
		//wait for every thread to finish
		for (Thread t : threads) {
			t.join();
		}

		System.out.println("Final Count = " + ct.getCount());
	}
}

我们正在使用Thread join()方法来确保每个线程都已死,然后再打印最终计数。

我们还使用Random类在run()方法中添加一些处理时间。

如果您运行上述程序,您会发现最终计数几乎每次都不同。

这种差异的原因是" count ++"语句。
这不是原子操作。

首先读取count变量,然后将其添加1,然后将值分配回count变量。

我们有多个线程同时处理count变量。
如果一个线程读取了count变量并且在可以更新它之前,另一个线程将对其进行更新。
这将导致程序中的数据损坏。

Java通过标记仅在任何时间点由一个线程执行的代码,在此情况下提供了synced关键字来帮助您。

Java同步示例

让我们使用synced关键字修复以上程序。
我们可以围绕" count ++"操作创建一个同步块。

synced关键字需要一个对象参数,该参数将用于创建锁定机制。
为此,我们可以在类中创建一个虚拟对象。

以下是更新的代码,该代码可以正常工作,最终计数为100。
我删除了通用代码,因此重点仅放在同步关键字的用法上。

public class CounterThread implements Runnable {
...
	private final Object mutex = new Object();
...

	public void run() {
...
		synchronized (mutex) {
			count++;
		}
	}
...
}

Java同步关键字示例

同步关键字如何在内部运作?

Java同步逻辑是围绕称为内部锁或者监视器锁的内部实体构建的。

当线程尝试进入同步区域时,它必须首先获取对象上的锁。
然后执行同步块中的所有语句。
最后,线程释放对象上的锁,等待池中的其他线程可以获取该锁。

如果对象为" null",则synced关键字将引发NullPointerException。

Java同步块

当代码块环绕在synced关键字周围时,称为同步块。

同步块的语法

synchronized (object) {
	//syhcnronized block code
}

这是Java中同步块的简单示例。

package com.theitroad.threads;

public class MyRunnable implements Runnable {

	private int counter;
	private final Object mutex = new Object();

	@Override
	public void run() {
		doSomething();
		synchronized (mutex) {
			counter++;
		}

	}

	private void doSomething() {
		//some heavy lifting work
	}

}

Java同步方法

有时,方法中的每个语句都需要同步。
在这种情况下,我们可以同步方法本身。

同步方法的语法

access_modifiers synchronized return_type method_name(method_parameters) {
	method_code
}

这是一个Java同步方法的示例。

package com.theitroad.threads;

public class MyRunnable implements Runnable {

	private int counter;

	@Override
	public void run() {
		increment(2);
	}

	private synchronized void increment(int i) {
		counter += i;
	}

}

用同步方法锁定对象

就像同步块一样,同步方法也需要一个对象来锁定。

  • 如果方法是静态的,则在类上获取锁。

  • 如果该方法是非静态的,则在当前对象上获取锁定。

Java同步方法与块

  • Java同步方法将锁定当前对象,因此,如果存在另一个同步方法,则即使这些方法中没有共享变量,其他线程也将等待该对象的锁定。
    Java同步块适用于对象字段,因此在这种情况下最好使用同步块。

  • 如果对象具有在相同变量上工作的多个同步方法,则首选同步方法。
    例如,StringBuffer使用同步方法,因为所有的append()和insert()方法都作用于同一对象。

这是一个示例,其中我们有多个方法作用于同一变量,因此使用同步方法是一个更好的选择。

package com.theitroad.threads;

public class MyRunnable implements Runnable {

	private int counter;

	@Override
	public void run() {
		increment(2);
		decrement(1);
	}

	private synchronized void increment(int i) {
		counter += i;
	}

	private synchronized void decrement(int i) {
		counter -= i;
	}
}

这是另一个示例,其中不同的方法正在不同的共享变量上工作,因此使用同步块是更好的选择。

package com.theitroad.threads;

public class MyRunnable implements Runnable {

	private int positiveCounter;
	private int negativeCounter;

	private final Object positiveCounterMutex = new Object();
	private final Object negativeCounterMutex = new Object();

	@Override
	public void run() {
		increment(2);
		decrement(1);
	}

	private void increment(int i) {
		synchronized (positiveCounterMutex) {
			positiveCounter += i;
		}
	}

	private void decrement(int i) {
		synchronized (negativeCounterMutex) {
			negativeCounter -= i;
		}
	}
}