BOL states:
TABLOCKX
Specifies that an exclusive lock is taken on the table. If HOLDOCK is also specified, the lock is held until the transaction completes
So it's not stated explicitly that without HOLDLOCK the lock is held until end-of-statement. But as far as my english - or logic - goes, I understood it that way. Otherwise - why to mention HOLDLOCK if TABLOCKX itself does the same? Let's check. Before opening transaction, I'll make some preparations.
CREATE DATABASE Demo
GO
USE [Demo]
GO
CREATE TABLE TabLockDemo( col1 INT, col2 VARCHAR(8000) )
GO
INSERT INTO TabLockDemo
VALUES(1, 'a')
GO
SELECT OBJECT_ID('TabLockDemo')
-- 2073058421
SELECT @@SPID
-- 52
Now let's set isolation level to read committed, open transaction and select data from a table using TABLOCKX hint:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
SELECT TOP 1 * FROM TabLockDemo WITH(TABLOCKX)
Now, in order to be sure that the statement ended, I would even execute another statement:
SELECT TOP 1 * FROM sys.indexes
Now let's see, what sp_lock tells us... The table is locked! You don't believe? Open another session and try to execute SELECT * FROM TabLockDemo.
The reason for this behaviour is pretty obvious. Exclusive lock is usually being used for DML operations - not for SELECT. But if we open transaction, execute some INSERT statement and hold the lock only for the duration of a statement, then we can now read this data from another session - the table isn't locked. Afterwards we rollback the first session and so we've got classical dirty read! Thus exclusive lock is always being held until the end of transaction - without any need of HOLDLOCK. BOL is at least misleading.