Java Synchronised变量
本文将讨论如何在Java中同步或锁定变量。
同步或锁定在多线程编程中至关重要。 并行运行的线程可能会尝试访问相同的变量或其他资源,从而产生意外的结果。
同步或锁定是避免此类错误情况的解决方案。
synchronized 关键字
同步是Java中的传统方法。 同步块是使用 synchronized
关键字实现的。
一旦一个线程进入同步代码,其他Java线程就会处于阻塞状态。 当前Java线程退出同步块后,其他Java线程可以尝试访问同步块。
这种方法有一个缺点。 同步块不允许线程在当前Java线程完成工作后在队列中等待并访问变量,因此Java线程必须等待很长时间。
将 Synchronized 关键字与方法或块一起使用
在下面的示例中,我们在 MultiThreadList 类的 run()
方法中同步 MenuObj。 我们还可以使用synchronized 定义 listItem()
代码块。
两个函数都会给出相同的结果。
同一个程序可以采用如下的同步方法。
public synchronized void listItem(String item){
{/*Code to Synchronize*/}
}
synchronized(this)
{
System.out.println("\nMenu Item - \t" + item);
try{
Thread.sleep(1000);
}
catch (Exception e){
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
在这里,用户可以看到使用同步块的完整示例代码。
示例代码:
import java.io.*;
import java.util.*;
//Menu class
class Menu
{
//List an item
public void listItem(String item)
{
System.out.println("\nMenu Item - " + item);
try {
Thread.sleep(1000);
}
catch (Exception e){
e.printStackTrace();
}
System.out.println("Listed - " + item);
}
}
//Multi-thread menu listing
class MultiThreadList extends Thread
{
private String item;
Menu MenuObj;
/* Gets menu object and a string item*/
MultiThreadList(String m, Menu obj)
{
item=m;
MenuObj=obj;
}
public void run() {
/* Only one Java thread can list an item at a time.*/
synchronized(MenuObj){
//Menu object synchronized
MenuObj.listItem(item);
}}}
//Main
class MainSync
{
public static void main(String args[])
{
Menu listItem=new Menu();
MultiThreadList M1=
new MultiThreadList( "Rice" , listItem );
MultiThreadList M2=
new MultiThreadList( "Icecream" , listItem );
// Start two threads
M1.start();
M2.start();
//Wait for thread completion
try{
M1.join();
M2.join();
}
catch(Exception e){
e.printStackTrace();
}}}
输出:
Menu Item - Rice
Listed - Rice
Menu Item - Icecream
Listed - Icecream
在上面的输出中,用户可以观察到,当一个线程访问 MenuObj 时,其他线程无法访问,因为它会打印列出的项目,然后打印菜单项。 如果多个线程尝试同时访问同一块,则它应该以不同的顺序打印菜单项和列出的项目。
使用 ReentrantLock 方法
Java中的 ReentrantLock
类是一种灵活的锁定变量的方法。 ReentrantLock 类在访问公共资源或变量时提供同步。
lock()
和 unlock()
方法执行该过程。 一次一个 Java 线程获取锁。 其他线程在此期间将处于阻塞状态。
线程可以多次进入锁。 因此,名称是可重入的。 当 Java 线程获得锁时,保持计数为 1,并且计数在重新进入时累加。
运行unlock() 后,锁定保持计数减一。 ReentrantLock 根据等待时间为线程提供服务。
等待时间较长的Java线程优先。
为了在出现某些异常时释放锁,lock() 在 try 块之前运行。 unblock() 在finally 块内运行。
ReentrantLock 函数
lock() | 将计数递增一。 如果该变量是空闲的,则为 Java 线程分配一个锁。 |
unlock() | 将计数减一。 当保持计数为零时锁定释放。 |
tryLock() | 如果资源空闲,该函数返回true。 否则,线程退出。 |
lockInterruptically() | 当一个 Java 线程使用锁时,其他 Java 线程可以中断该 Java 线程。 因此,当前的 Java 线程必须立即返回。 |
getHoldCount() | 返回资源上的锁数量。 |
isHeldByCurrentThread() | 当当前Java线程使用锁时,该方法返回true。 |
使用 ReentrantLock
在这个程序中,我们为ReentrantLock创建一个对象。 runnable类业务执行并将锁传递给ReentrantLock。
有两个池可以观察结果。 程序使用 lock()
获取锁,使用 unlock()
最终释放锁。
布尔完成和可用跟踪锁的可用性和任务完成情况。
有两个线程正在运行。 第二家商店没有获得锁并在队列中等待。
Shop 1 首先获得外锁,然后获得内锁。 此时2号店正在等待。
Shop 1 的锁定计数为 2。 店铺 1 释放内部锁,然后释放外部锁,然后锁保持计数减少,店铺 1 关闭。
Shop2通过队列自动获得锁。 对商店 2 重复该过程。
示例代码:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
class business implements Runnable
{
String name;
ReentrantLock re;
public business(ReentrantLock rl, String n)
{
re=rl;
name=n;
}
public void run()
{
boolean finished=false;
while (!finished)
{
//Get Outer Lock
boolean isLockAvailable = re.tryLock();
//If the lock is available
if(isLockAvailable)
{
try{
Date d=new Date();
SimpleDateFormat ft=new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - "+ name
+ " got outside lock at "
+ ft.format(d)
);
Thread.sleep(1500);
// Get Inner Lock
re.lock();
try{
d=new Date();
ft=new SimpleDateFormat("hh:mm:ss");
System.out.println("Shop - "+ name
+ " got inside lock at "
+ ft.format(d)
);
System.out.println("Lock Hold Count - "+ re.getHoldCount());
Thread.sleep(1500);
}
catch(InterruptedException e){
e.printStackTrace();
}
finally
{
//Inner lock release
System.out.println("Shop - " + name +
" releasing inside lock");
re.unlock();
}
System.out.println("Lock Hold Count - " + re.getHoldCount());
System.out.println("Shop - " + name + " closed");
finished=true;
}
catch(InterruptedException e){
e.printStackTrace();
}
finally{
//Outer lock release
System.out.println("Shop - " + name +
" releasing outside lock");
re.unlock();
System.out.println("Lock Hold Count - " +
re.getHoldCount());
}
}
else
{
System.out.println("Shop - " + name +
" waiting for lock");
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
public class sample
{
static final int POOL_MAX=2;
public static void main(String[] args)
{
ReentrantLock rel=new ReentrantLock();
ExecutorService pool=Executors.newFixedThreadPool(POOL_MAX);
Runnable p1=new business(rel, "Shop 1");
Runnable p2=new business(rel, "Shop 2");
System.out.println("Running Pool 1");
pool.execute(p1);
System.out.println("Running Pool 2");
pool.execute(p2);
pool.shutdown();
}
}
输出:
Running Pool 1
Running Pool 2
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the outside lock at 11:05:47
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the inside 'lock' at 11:05:48
Lock Hold Count - 2
Shop - Shop 2 waiting for the lock
Shop - Shop 2 waiting for the lock
Shop - Shop 1 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 1 closed
Shop - Shop 1 releasing the outside lock
Lock Hold Count - 0
Shop - Shop 2 got the outside lock at 11:05:51
Shop - Shop 2 got the inside 'lock' at 11:05:52
Lock Hold Count - 2
Shop - Shop 2 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 2 closed
Shop - Shop 2 releasing the outside lock
Lock Hold Count - 0
使用二进制或计数信号量
Java中的信号量决定了一起访问资源的线程数量。 二进制信号量在多线程编程中提供访问资源的通行证或许可。
Java 线程需要等待,直到锁许可可用。 acquire()
方法给出通行证,release()
函数释放锁通行证。
acquire()
函数会阻塞线程,直到锁许可变得可用。
信号量中可用的两种状态是许可证可用和许可证不可用。
在此示例中,Semaphore 对象访问 commonResource()
函数。 程序中正在运行两个线程。
acquire()
方法向线程授予许可,release()
方法根据可用性释放锁许可。
线程0获得通行证并进入繁忙空间。 然后,在释放许可证后,它就会进入自由空间。
接下来,线程1获得锁许可,进入临界区,释放许可后来到空闲空间。
示例代码:
import java.util.concurrent.Semaphore;
public class SemaphoreCounter {
Semaphore binary=new Semaphore(1);
public static void main(String args[]) {
final SemaphoreCounter semObj=new SemaphoreCounter();
new Thread(){
@Override
public void run(){
semObj.commonResource();
}
}.start();
new Thread(){
@Override
public void run(){
semObj.commonResource();
}
}.start();
}
private void commonResource() {
try {
binary.acquire();
//mutual sharing resource
System.out.println(Thread.currentThread().getName() + " busy space");
} catch (InterruptedException ie) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " free space");
}
}
}
输出:
Thread-0 busy space
Thread-0 free space
Thread-1 busy space
Thread-1 free space
使用原子变量
原子变量通过其内置函数在 Java 多线程编程中提供同步。
该程序设置计时器限制和线程大小。 executorService 通过循环到线程限制来提交计时器并关闭服务。
Atomic变量的定时器对象增加了定时器。 整个过程以同步方式发生。
每个 Java 线程都会获得唯一的计数而不会中断。
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
class Timer implements Runnable {
private static AtomicInteger timer;
private static final int timerLimit=10;
private static final int totThreads=5;
public static void main(String[] args) {
timer = new AtomicInteger(0);
ExecutorService executorService = Executors.newFixedThreadPool(totThreads);
for (int i = 0; i < totThreads; i++) {
executorService.submit(new Timer());
}
executorService.shutdown();
}
@Override
public void run() {
while (timer.get() < timerLimit) {
increaseTimer();
}
}
private void increaseTimer() {
System.out.println(Thread.currentThread().getName() + " : " + timer.getAndIncrement());
}
}
输出:
pool-1-thread-2 : 4
pool-1-thread-2 : 5
pool-1-thread-4 : 1
pool-1-thread-4 : 7
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 3
pool-1-thread-5 : 2
pool-1-thread-1 : 0
pool-1-thread-2 : 6
本文教会了我们在多线程编程中Java中同步资源的四种方法。 同步方法是传统方法。
我们采用 ReentrantLock 方法来提高灵活性。 ReentrantLock也有一个缺点:程序员在编写 lock()
和 unlock()
时需要跟踪try-catch-finally块。
尽管 Binary Semaphore 与 ReentrantLock 类似,但 ReentrantLock 仅具有基础级别的同步和固定锁定机制。 二进制信号量提供高级同步、自定义锁定和死锁预防。
原子变量也是一种实现简单数据读写活动同步的方法。
相关文章
如何在 Java 中延迟几秒钟的时间
发布时间:2023/12/17 浏览次数:217 分类:Java
-
本篇文章主要介绍如何在 Java 中制造程序延迟。本教程介绍了如何在 Java 中制造程序延时,并列举了一些示例代码来了解它。
如何在 Java 中把 Hashmap 转换为 JSON 对象
发布时间:2023/12/17 浏览次数:187 分类:Java
-
它描述了允许我们将哈希图转换为简单的 JSON 对象的方法。本文介绍了在 Java 中把 Hashmap 转换为 JSON 对象的方法。我们将看到关于创建一个 hashmap,然后将其转换为 JSON 对象的详细例子。
如何在 Java 中按值排序 Map
发布时间:2023/12/17 浏览次数:171 分类:Java
-
本文介绍了如何在 Java 中按值对 Map 进行排序。本教程介绍了如何在 Java 中按值对 Map
进行排序,并列出了一些示例代码来理解它。
如何在 Java 中打印 HashMap
发布时间:2023/12/17 浏览次数:192 分类:Java
-
本帖介绍了如何在 Java 中打印 HashMap。本教程介绍了如何在 Java 中打印 HashMap 元素,还列举了一些示例代码来理解这个主题。
在 Java 中更新 Hashmap 的值
发布时间:2023/12/17 浏览次数:146 分类:Java
-
本文介绍了如何在 Java 中更新 HashMap 中的一个值。本文介绍了如何在 Java 中使用 HashMap 类中包含的两个方法-put() 和 replace() 更新 HashMap 中的值。
Java 中的 hashmap 和 map 之间的区别
发布时间:2023/12/17 浏览次数:79 分类:Java
-
本文介绍了 Java 中的 hashmap 和 map 接口之间的区别。本教程介绍了 Java 中 Map 和 HashMap 之间的主要区别。在 Java 中,Map 是用于以键值对存储数据的接口,
在 Java 中获取用户主目录
发布时间:2023/12/17 浏览次数:218 分类:Java
-
这篇文章向你展示了如何在 Java 中获取用户主目录。本教程介绍了如何在 Java 中获取用户主目录,并列出了一些示例代码以指导你完成该主题。
Java 中 size 和 length 的区别
发布时间:2023/12/17 浏览次数:179 分类:Java
-
这篇文章教你如何知道 Java 中大小和长度之间的区别。本教程介绍了 Java 中大小和长度之间的区别。我们还列出了一些示例代码以帮助你理解该主题。
Java 中的互斥锁
发布时间:2023/12/17 浏览次数:111 分类:Java
-
了解有关 Java 中互斥锁的一切,在计算机科学领域,互斥或互斥被称为并发控制的属性。每台计算机都使用称为线程的最小程序指令序列。有一次,计算机在一个线程上工作。为了更好地理解,