```python
import threading
import time
from collections import OrderedDict
from typing import Any, Dict, Optional, Tuple

class LRUCache:
    def __init__(self, max_size: int, default_ttl: float = 60.0):
        if max_size <= 0:
            raise ValueError("max_size must be positive")
        self._max_size = max_size
        self._default_ttl = default_ttl
        self._cache: OrderedDict[int, Tuple[Any, float]] = OrderedDict()
        self._lock = threading.RLock()

    def get(self, key: int) -> Optional[Any]:
        with self._lock:
            if key not in self._cache:
                return None
            
            value, expiry = self._cache[key]
            current_time = time.time()
            
            if current_time > expiry:
                del self._cache[key]
                return None
            
            self._cache.move_to_end(key)
            return value

    def put(self, key: int, value: Any, ttl: Optional[float] = None) -> None:
        if ttl is None:
            ttl = self._default_ttl
        
        expiry = time.time() + ttl
        
        with self._lock:
            if key in self._cache:
                self._cache.move_to_end(key)
                self._cache[key] = (value, expiry)
                return
            
            if len(self._cache) >= self._max_size:
                self._cache.popitem(last=False)
            
            self._cache[key] = (value, expiry)

    def clear(self) -> None:
        with self._lock:
            self._cache.clear()

    def __len__(self) -> int:
        with self._lock:
            return len(self._cache)

    def __contains__(self, key: int) -> bool:
        with self._lock:
            if key not in self._cache:
                return False
            value, expiry = self._cache[key]
            if time.time() > expiry:
                return False
            return True

# Pytest tests
import pytest

def test_basic_put_get():
    cache = LRUCache(max_size=2)
    cache.put(1, "one")
    cache.put(2, "two")
    assert cache.get(1) == "one"
    assert cache.get(2) == "two"
    assert cache.get(3) is None

def test_lru_eviction():
    cache = LRUCache(max_size=2)
    cache.put(1, "one")
    cache.put(2, "two")
    cache.get(1)  # Access 1 to make it recently used
    cache.put(3, "three")  # Should evict 2
    assert cache.get(1) == "one"
    assert cache.get(2) is None
    assert cache.get(3) == "three"

def test_ttl_expiration():
    cache = LRUCache(max_size=2, default_ttl=0.1)
    cache.put(1, "one")
    assert cache.get(1) == "one"
    time.sleep(0.15)
    assert