Index: src/bp-messages/bp-messages-cache.php
===================================================================
--- src/bp-messages/bp-messages-cache.php
+++ src/bp-messages/bp-messages-cache.php
@@ -46,31 +46,45 @@
 add_action( 'messages_screen_inbox',   'bp_core_clear_cache' );
 
 /**
- * Clear unread count cache for each recipient after a message is sent.
+ * Clear message cache after a message is saved.
  *
  * @since BuddyPress (2.0.0)
  *
  * @param BP_Messages_Message $message
  */
-function bp_messages_clear_unread_count_cache_on_message_save( BP_Messages_Message $message ) {
+function bp_messages_clear_cache_on_message_save( BP_Messages_Message $message ) {
+	// Delete thread cache
+	wp_cache_delete( $message->thread_id, 'bp_messages_threads' );
+
+	// Delete unread count for each recipient
 	foreach ( (array) $message->recipients as $recipient ) {
 		wp_cache_delete( $recipient->user_id, 'bp_messages_unread_count' );
 	}
+
+	// Delete thread recipient cache
+	wp_cache_delete( 'thread_recipients_' . $message->thread_id, 'bp_messages' );
 }
-add_action( 'messages_message_after_save', 'bp_messages_clear_unread_count_cache_on_message_save' );
+add_action( 'messages_message_after_save', 'bp_messages_clear_cache_on_message_save' );
 
 /**
- * Clear unread count cache for the logged-in user after a message is deleted.
+ * Clear message cache after a message thread is deleted.
  *
  * @since BuddyPress (2.0.0)
  *
  * @param int|array $thread_ids If single thread, the thread ID. Otherwise, an
  *  array of thread IDs
  */
-function bp_messages_clear_unread_count_cache_on_message_delete( $thread_ids ) {
+function bp_messages_clear_cache_on_message_delete( $thread_ids ) {
+	// Delete thread and thread recipient cache
+	foreach( (array) $thread_ids as $thread_id ) {
+		wp_cache_delete( $thread_id, 'bp_messages_threads' );
+		wp_cache_delete( "thread_recipients_{$thread_id}", 'bp_messages' );
+	}
+
+	// Delete unread count for logged-in user
 	wp_cache_delete( bp_loggedin_user_id(), 'bp_messages_unread_count' );
 }
-add_action( 'messages_before_delete_thread', 'bp_messages_clear_unread_count_cache_on_message_delete' );
+add_action( 'messages_delete_thread', 'bp_messages_clear_cache_on_message_delete' );
 
 /**
  * Invalidate cache for notices.
@@ -84,29 +98,3 @@
 }
 add_action( 'messages_notice_after_save',    'bp_notices_clear_cache' );
 add_action( 'messages_notice_before_delete', 'bp_notices_clear_cache' );
-
-/**
- * Invalidate thread recipient cache on message update.
- *
- * @since BuddyPress (2.3.0)
- *
- * @param BP_Messages_Message $message Message object.
- */
-function bp_messages_clear_message_thread_recipient_cache_on_message_sent( BP_Messages_Message $message ) {
-	wp_cache_delete( 'thread_recipients_' . $message->thread_id, 'bp_messages' );
-}
-add_action( 'messages_message_sent', 'bp_messages_clear_message_thread_recipient_cache_on_message_sent' );
-
-/**
- * Invalidate thread recipient cache on thread deletion.
- *
- * @since BuddyPress (2.3.0)
- *
- * @param int|array $thread_ids IDs of deleted threads.
- */
-function bp_messages_clear_message_thread_recipient_cache_on_thread_delete( $thread_ids ) {
-	foreach ( (array) $thread_ids as $thread_id ) {
-		wp_cache_delete( 'thread_recipients_' . $thread_id, 'bp_messages' );
-	}
-}
-add_action( 'messages_delete_thread', 'bp_messages_clear_message_thread_recipient_cache_on_thread_delete' );
Index: src/bp-messages/bp-messages-loader.php
===================================================================
--- src/bp-messages/bp-messages-loader.php
+++ src/bp-messages/bp-messages-loader.php
@@ -303,6 +303,7 @@
 		// Global groups
 		wp_cache_add_global_groups( array(
 			'bp_messages',
+			'bp_messages_threads',
 			'bp_messages_unread_count',
 			'message_meta'
 		) );
Index: src/bp-messages/classes/class-bp_messages-thread.php
===================================================================
--- src/bp-messages/classes/class-bp_messages-thread.php
+++ src/bp-messages/classes/class-bp_messages-thread.php
@@ -147,12 +147,18 @@
 		$this->messages_order = $order;
 		$this->thread_id      = $thread_id;
 
-		$bp = buddypress();
+		// get messages for thread
+		$this->messages = self::get_messages( $thread_id );
 
-		if ( !$this->messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_messages} WHERE thread_id = %d ORDER BY date_sent " . $order, $this->thread_id ) ) ) {
+		if ( empty( $this->messages ) || is_wp_error( $this->messages ) ) {
 			return false;
 		}
 
+		// flip if order is DESC
+		if ( 'DESC' === $order ) {
+			$this->messages = array_reverse( $this->messages );
+		}
+
 		foreach ( (array) $this->messages as $key => $message ) {
 			$this->sender_ids[$message->sender_id] = $message->sender_id;
 		}
@@ -207,23 +213,28 @@
 	 *
 	 * @since BuddyPress (1.0.0)
 	 *
+	 * @param int $thread_id The thread ID
 	 * @return array
 	 */
-	public function get_recipients() {
+	public function get_recipients( $thread_id = 0 ) {
 		global $wpdb;
 
-		$recipients = wp_cache_get( 'thread_recipients_' . $this->thread_id, 'bp_messages' );
+		if ( empty( $thread_id ) ) {
+			$thread_id = $this->thread_id;
+		}
+
+		$recipients = wp_cache_get( 'thread_recipients_' . $thread_id, 'bp_messages' );
 		if ( false === $recipients ) {
 			$bp = buddypress();
 
 			$recipients = array();
-			$results    = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $this->thread_id ) );
+			$results    = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $thread_id ) );
 
 			foreach ( (array) $results as $recipient ) {
 				$recipients[ $recipient->user_id ] = $recipient;
 			}
 
-			wp_cache_set( 'thread_recipients_' . $this->thread_id, $recipients, 'bp_messages' );
+			wp_cache_set( 'thread_recipients_' . $thread_id, $recipients, 'bp_messages' );
 		}
 
 		/**
@@ -234,12 +245,50 @@
 		 * @param array $recipients Array of recipient objects.
 		 * @param int   $thread_id  ID of the current thread.
 		 */
-		return apply_filters( 'bp_messages_thread_get_recipients', $recipients, $this->thread_id );
+		return apply_filters( 'bp_messages_thread_get_recipients', $recipients, $thread_id );
 	}
 
 	/** Static Functions ******************************************************/
 
 	/**
+	 * Get all messages associated with a thread.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @param int $thread_id The message thread ID
+	 * @return array
+	 */
+	public static function get_messages( $thread_id = 0 ) {
+		$messages = wp_cache_get( $thread_id, 'bp_messages_threads' );
+
+		if ( false === $messages ) {
+			global $wpdb;
+
+			$bp = buddypress();
+
+			// always sort by ASC by default
+			$messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_messages} WHERE thread_id = %d ORDER BY date_sent ASC", $thread_id ) );
+
+			wp_cache_set( $thread_id, (array) $messages, 'bp_messages_threads' );
+		}
+
+		return $messages;
+	}
+
+	/**
+	 * Static method to get message recipients by thread ID.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @param  int $thread_id The thread ID
+	 * @return array
+	 */
+	public static function get_recipients_for_thread( $thread_id = 0 ) {
+		$thread = new self( false );
+		return $thread->get_recipients( $thread_id );
+	}
+
+	/**
 	 * Mark messages in a thread as deleted or delete all messages in a thread.
 	 *
 	 * Note: All messages in a thread are deleted once every recipient in a thread
@@ -644,17 +693,20 @@
 	 *
 	 * @param int $thread_id The message thread ID.
 	 * @param int $user_id The user ID.
-	 * @return int The message ID on success.
+	 * @return int|null The recorded recipient ID on success, null on failure
 	 */
 	public static function check_access( $thread_id, $user_id = 0 ) {
-		global $wpdb;
-
-		if ( empty( $user_id ) )
+		if ( empty( $user_id ) ) {
 			$user_id = bp_loggedin_user_id();
+		}
 
-		$bp = buddypress();
+		$recipients = self::get_recipients_for_thread( $thread_id );
 
-		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d AND is_deleted = 0 AND user_id = %d", $thread_id, $user_id ) );
+		if ( isset( $recipients[$user_id] ) && 0 == $recipients[$user_id]->is_deleted ) {
+			return $recipients[$user_id]->id;
+		} else {
+			return null;
+		}
 	}
 
 	/**
@@ -663,19 +715,21 @@
 	 * @since BuddyPress (1.0.0)
 	 *
 	 * @param int $thread_id The message thread ID.
-	 * @return int The message thread ID on success.
+	 * @return int|null The message thread ID on success, null on failure
 	 */
 	public static function is_valid( $thread_id = 0 ) {
-		global $wpdb;
-
 		// Bail if no thread ID is passed
 		if ( empty( $thread_id ) ) {
 			return false;
 		}
 
-		$bp = buddypress();
+		$thread = self::get_messages( $thread_id );
 
-		return $wpdb->get_var( $wpdb->prepare( "SELECT thread_id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d LIMIT 1", $thread_id ) );
+		if ( ! empty( $thread ) ) {
+			return $thread_id;
+		} else {
+			return null;
+		}
 	}
 
 	/**
Index: tests/phpunit/testcases/messages/class.bp-messages-thread.php
===================================================================
--- tests/phpunit/testcases/messages/class.bp-messages-thread.php
+++ tests/phpunit/testcases/messages/class.bp-messages-thread.php
@@ -4,6 +4,71 @@
  * @group BP_Messages_Thread
  */
 class BP_Tests_BP_Messages_Thread extends BP_UnitTestCase {
+
+	/**
+	 * @group cache
+	 */
+	public function test_construct_cache() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		// prime cache
+		new BP_Messages_Thread( $t1 );
+
+		// Cache should exist
+		$this->assertThat(
+			wp_cache_get( $t1, 'bp_messages_threads' ),
+			$this->logicalNot( $this->equalTo( false ) ),
+			'Message thread cache should exist.'
+		);
+	}
+
+	/**
+	 * @group order
+	 */
+	public function test_construct_order_desc() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+		// save message ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$m1 = wp_list_pluck( $thread->messages, 'id' );
+		$m1 = array_pop( $m1 );
+
+		// create reply
+		$t2 = $this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'content' => 'Bar'
+		) );
+		// save message ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$m2 = wp_list_pluck( $thread->messages, 'id' );
+		$m2 = array_pop( $m2 );
+
+		// now get thread by DESC
+		$thread = new BP_Messages_Thread( $t1, 'DESC' );
+
+		// assert!
+		$this->assertEquals(
+			array( $m2, $m1 ),
+			wp_list_pluck( $thread->messages, 'id' )
+		);
+	}
+
 	/**
 	 * @group get_current_threads_for_user
 	 */
@@ -286,4 +351,55 @@
 
 		$this->set_current_user( $current_user );
 	}
+
+	/**
+	 * @group check_access
+	 */
+	public function test_check_access_valid_thread() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		// save recipient ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$r1 = wp_list_pluck( $thread->recipients, 'id' );
+		$r1 = array_pop( $r1 );
+
+		$this->assertEquals( $r1, BP_Messages_Thread::check_access( $t1, $u1 ) );
+	}
+
+	/**
+	 * @group check_access
+	 */
+	public function test_check_access_invalid_thread() {
+		$this->assertEquals( null, BP_Messages_Thread::check_access( 999, 1 ) );
+	}
+
+	/**
+	 * @group is_valid
+	 */
+	public function test_is_valid_valid_thread() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$this->assertEquals( $t1, BP_Messages_Thread::is_valid( $t1 ) );
+	}
+
+	/**
+	 * @group is_valid
+	 */
+	public function test_is_valid_invalid_thread() {
+		$this->assertEquals( null, BP_Messages_Thread::is_valid( 999 ) );
+	}
 }
