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; } } }