Java同步关键字,同步方法和块
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;
}
}
}

