迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > Java >

Java Synchronised变量

作者:迹忆客 最近更新:2023/07/16 浏览次数:

本文将讨论如何在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 仅具有基础级别的同步和固定锁定机制。 二进制信号量提供高级同步、自定义锁定和死锁预防。

原子变量也是一种实现简单数据读写活动同步的方法。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

在 Java 中对一个 Switch Case 语句使用多个值

发布时间:2023/07/16 浏览次数:172 分类:Java

在本文中,我们将学习如何在一个 switch-case 语句中使用多个值。使用 switch-case 语句 Java 允许程序员通过使用 switch case 语句来像其他编程语言一样克服太多的 if-else 条件语句。

Java 中的线程安全延迟初始化

发布时间:2023/07/16 浏览次数:59 分类:Java

本文将讨论在 Java 中实现线程安全的延迟初始化。Java 中的对象初始化 延迟初始化是延迟对象创建的行为。 它还可能导致某些计算任务或首次昂贵流程的延迟。

在 Java 中显示动画 GIF

发布时间:2023/07/16 浏览次数:112 分类:Java

我们可以使用javax包的Swing库方法来在Java中显示动画GIF。 本文介绍用户如何在 Java 应用程序或单独的窗口中显示动画 GIF。使用 Javax.swing 库在 Java 中显示动画 GIF

在 Java 中用 %20 替换空格

发布时间:2023/07/16 浏览次数:96 分类:Java

在本文中,我们将学习两种用 %20 替换给定字符串的所有空格的方法。Java中使用replaceAll()方法将空格替换为%20 在这里,我们使用Java内置方法 replaceAll() 将所有空格替换为%20字符串。

Java 中的矩阵乘法

发布时间:2023/07/16 浏览次数:99 分类:Java

在本文中,我们将学习在 Java 中将两个矩阵相乘。Java 中两个矩阵相乘 我们使用乘法和加法运算符来乘两个矩阵。

Java 聚合与组合

发布时间:2023/07/16 浏览次数:67 分类:Java

在Java中,聚合和组合是紧密相连的两个概念。 组合是类之间的紧密关联,而聚合是弱关联。Java 中的组合 Java 中的聚合

Java 错误 Java.Security.InvalidKeyException: Illegal Key Size

发布时间:2023/07/15 浏览次数:98 分类:Java

本篇文章介绍包含 java.security.InvalidKeyException: Illegal key size 的 Java 代码。 然后,我们将了解其可能的原因。最后,它通过消除指定的错误来引导我们找到解决方案。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便