Code Locking

BoxLang provides powerful thread-safe locking mechanisms for synchronizing access to shared resources with named locks, scope locks, and full Java interop support.

Locking is essential for multi-threaded applications where multiple requests or threads access shared resources. BoxLang's locking system uses Java's ReentrantReadWriteLock internally, providing robust, high-performance synchronization with support for both exclusive (write) and readonly (read) locks.

📋 Table of Contents

🎯 Lock Basics

BoxLang provides the lock component/construct for thread-safe synchronization:

// Script syntax
lock name="mylock" timeout=10 {
    // Synchronized code here
    criticalOperation()
}

// Template syntax
<bx:lock name="mylock" timeout="10">
    <!-- Synchronized code here -->
</bx:lock>

// CFML compatibility
<cflock name="mylock" timeout="10">
    <!-- Synchronized code here -->
</cflock>

When to Use Locks

Use locks when:

  • ✅ Multiple threads access shared data

  • ✅ Writing to shared variables (application, server, session scopes)

  • ✅ Reading shared data that might be modified

  • ✅ Ensuring atomic operations

  • ✅ Preventing race conditions

  • ✅ Coordinating access to external resources

Lock Flow Diagram

🔐 Lock Types

BoxLang supports two lock types based on Java's ReentrantReadWriteLock:

1. Exclusive Locks (Write Locks)

Exclusive locks allow only ONE thread to access the locked code at a time. Use for read/write operations on shared data.

Characteristics:

  • ✅ Single-thread access only

  • ✅ Blocks all other threads (both exclusive and readonly)

  • ✅ Use for writing or modifying shared data

  • ✅ First-come, first-served basis

2. Readonly Locks (Read Locks)

Readonly locks allow MULTIPLE threads to access the locked code simultaneously. Use for read-only operations on shared data.

Characteristics:

  • ✅ Multiple concurrent readers allowed

  • ✅ Blocked by exclusive locks

  • ✅ Use for reading shared data

  • ✅ Better performance for read-heavy workloads

Lock Type Comparison

Feature
Exclusive
Readonly

Concurrent Access

❌ No (single thread)

✅ Yes (multiple threads)

Blocks Exclusive

✅ Yes

✅ Yes

Blocks Readonly

✅ Yes

❌ No

Use For

Writing/Modifying

Reading only

Performance

Lower throughput

Higher throughput

Default

✅ Yes

❌ No

Lock Type Interactions

📛 Named Locking

Named locks use a string identifier to coordinate access across the entire BoxLang server.

Basic Named Lock

Naming Conventions

Use descriptive, namespaced lock names to avoid collisions:

Lock Name Characteristics

  • Case-Sensitive: "MyLock" and "mylock" are different locks

  • Server-Wide: Shared across all applications on the server

  • Dynamic: Can use variables/expressions in names

  • Must be Non-Empty: Empty strings not allowed

Named Lock Examples

Example 1: Cache Population

Example 2: Counter Increment

Example 3: Multiple Locks with Different Names

🎯 Scope Locking

Scope locks synchronize access to entire BoxLang scopes: application, server, session, request.

Scope Lock Syntax

How Scope Locks Work

Each scope has a unique internal lock name generated from the scope name and instance hash:

This ensures each scope instance (e.g., each session) has its own lock.

Available Scopes

Scope
Lock Granularity
Use Case

application

Per application

Application-wide shared data

server

Per server

Server-wide shared data

session

Per session

User-specific session data

request

Per request

Request-scoped coordination

Scope Lock Examples

Example 1: Application Counter

Example 2: Session Data Update

Example 3: Server-Wide Configuration

Scope Lock vs Named Lock

⚙️ Lock Attributes

Complete reference for all lock component attributes:

Attribute
Type
Required
Default
Description

name

string

⚠️ *

-

Lock name (server-wide unique identifier). Cannot be empty.

scope

string

⚠️ *

-

Scope to lock (application, server, session, request)

timeout

integer

✅ Yes

-

Maximum seconds to wait for lock. 0 = wait forever.

type

string

❌ No

"exclusive"

Lock type: "exclusive" or "readonly"

throwOnTimeout

boolean

❌ No

true

Throw LockException on timeout?

* Mutually Exclusive: Must provide either name OR scope, but not both.

Attribute Details

name - Lock Name

  • Server-wide unique identifier

  • Case-sensitive

  • Can use expressions: name="lock-#userId#"

  • Cannot be empty string

  • Mutually exclusive with scope

scope - Scope Name

  • Values: application, server, session, request

  • Locks entire scope

  • Mutually exclusive with name

timeout - Wait Time

  • Positive integer: Wait N seconds for lock

  • Zero (0): Wait forever (until lock acquired)

  • If lock not acquired within timeout, behavior depends on throwOnTimeout

Timeout Best Practice: Always use the minimum timeout necessary. Large timeouts can block threads and reduce throughput.

type - Lock Type

  • "exclusive" (default): Single-thread access (write lock)

  • "readonly": Multi-thread access (read lock)

  • Case-insensitive

throwOnTimeout - Timeout Behavior

  • true (default): Throws LockException if timeout reached

  • false: Skips lock body silently and continues execution

Attribute Examples

🔄 Double-Check Locking Pattern

The double-check locking pattern prevents race conditions where multiple threads check a condition, then compete to initialize a resource.

The Problem: Race Condition

Problem: If 10 threads are waiting at the lock, all 10 will execute the expensive operation!

The Solution: Double-Check Pattern

Double-Check Pattern Diagram

Real-World Examples

Example 1: Cache Population

Example 2: Singleton Initialization

Example 3: Application Initialization

Why Double-Check Works

  1. First Check (outside lock): Fast path - avoids lock overhead when value exists

  2. Second Check (inside lock): Prevents race condition - ensures only one thread initializes

  3. Lock Protection: Guarantees atomic initialization

⚠️ Deadlock Prevention

A deadlock occurs when threads are waiting for locks held by each other, creating a cycle that cannot be broken.

Deadlock Example

Deadlock Diagram

Deadlock Prevention Strategies

1. Consistent Lock Ordering

Always acquire locks in the same order across all code:

2. Use Minimal Timeout

Always set reasonable timeouts so locks auto-release:

3. Avoid Nested Locks

Simplest solution - don't nest locks:

4. Use Lock Hierarchies

Establish a hierarchy and always lock from top to bottom:

Deadlock Recovery

BoxLang's locks automatically release on:

  • Timeout: Lock acquisition times out

  • Exception: Thread throws exception

  • Thread Termination: Thread ends abnormally

Deadlock Best Practices

Always use timeouts - Never use timeout=0 in production ✅ Consistent ordering - Document and enforce lock order ✅ Minimize lock scope - Lock smallest code section possible ✅ Avoid nesting - Use single locks when possible ✅ Use named locks - More precise than scope locks ✅ Log lock acquisitions - Track lock usage patterns ✅ Monitor lock times - Identify bottlenecks

☕ Advanced: Java Interop Locking

BoxLang's full Java interop allows you to use native Java synchronization mechanisms directly for advanced locking scenarios.

Using Java ReentrantLock

Java's ReentrantLock provides explicit lock/unlock control:

Using Java ReentrantReadWriteLock

BoxLang's lock component uses this internally:

Using Java Synchronized Blocks

Java's synchronized keyword via BoxLang:

Note: BoxLang doesn't have native synchronized keyword, but you can call Java synchronized methods or use Java lock objects directly.

Using Java Semaphores

Control access with permits:

Using Java CountDownLatch

Wait for multiple threads to complete:

Using Java CyclicBarrier

Synchronize threads at a barrier point:

Using Java Atomic Variables

Lock-free atomic operations:

Using Java ConcurrentHashMap

Thread-safe map without explicit locks:

Advanced Pattern: Custom Lock Manager

Comparison: BoxLang vs Java Locking

Feature

BoxLang lock

Java Locks

Use When

Syntax

Simple, declarative

Explicit lock/unlock

BoxLang: Simplicity

Auto-release

✅ Automatic

❌ Manual finally

BoxLang: Safety

Timeout

✅ Built-in

Manual

BoxLang: Deadlock prevention

Read/Write

✅ Built-in types

Manual handling

BoxLang: Simplicity

Advanced Control

❌ Limited

✅ Full control

Java: Fine-grained

Lock-free

❌ No

✅ Atomics

Java: Performance

Semaphores

❌ No

✅ Yes

Java: Permits

Barriers

❌ No

✅ Yes

Java: Coordination

Java Interop Examples

Example 1: Rate Limiting with Semaphore

Example 2: Atomic Counter

Example 3: Thread-Safe Cache

💡 Best Practices

1. Use Minimum Timeouts

2. Prefer Named Locks Over Scope Locks

3. Always Use Double-Check Pattern

4. Use Descriptive Lock Names

5. Handle Timeouts Appropriately

6. Use Readonly Locks for Read Operations

7. Keep Lock Scope Minimal

8. Document Lock Dependencies

9. Monitor Lock Performance

10. Use Java Locks for Advanced Scenarios

📋 Summary

BoxLang locking provides powerful thread synchronization:

Two Lock Types - Exclusive (write) and Readonly (read)

Named Locks - Server-wide locks by name

Scope Locks - Lock entire BoxLang scopes

Automatic Release - Locks release on timeout/exception

Timeout Control - Prevent infinite deadlocks

ReentrantReadWriteLock - High-performance Java locks internally

Java Interop - Full access to Java concurrency APIs

Deadlock Prevention - Built-in timeout mechanisms

Last updated

Was this helpful?