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 中对一个 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 代码。 然后,我们将了解其可能的原因。最后,它通过消除指定的错误来引导我们找到解决方案。
Java 错误 Java.SQL.SQLException: Access Denied for User Root@Localhost
发布时间:2023/07/15 浏览次数:72 分类:Java
-
本篇文章介绍如何解决 Java 中的 java.sql.SQLException: Access Denied for user 'root'@'localhost' 错误。修复 Java 中的 java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
Java 异常 Java.Lang.ClassNotFoundeException: Sun.Jdbc.Odbc.JdbcOdbcDriver
发布时间:2023/07/15 浏览次数:106 分类:Java
-
本篇文章介绍了 Java 中的 java.lang.ClassNotFoundException: sun.jdbc.odbc.JdbcOdbcDriver 错误。修复 Java 中的 java.lang.ClassNotFoundException: sun.jdbc.odbc.JdbcOdbcDriver