⑴ 談談你對Spring, Struts, Hibernate的理解。分開講最好。
各有各的好處哦
一搬 使用hibernate就是使用hibernate的映射機制哦對資料庫中的表映射
把一個表映射成一個類 表中的欄位就如類中的普通成員變數一樣 想操縱表就直接操作映射的類就行了這就使用大家更方便的使java和資料庫的連接。
而spring呢 是配置java連接資料庫的以及可以連接多個資料庫 能更好的配置資料庫的事物提交
使整個程序以多線程的方式連接資料庫哦。
struts的用處就多了哦 ,最主要的是可以使程序員AOC面向切面編程 以及對用戶的請求進行攔截
能更好的處理用戶請求。
希望對你了解有些幫助。
⑵ 使用Hibernate,進行多線程訪問資料庫,如用20個鏈接分別取執行sql語句
c3p0.numHelperThreads=3
⑶ 請簡述Hibernate工作原理
Hibernate工作原理是Configuration讀取Hibernate的配置文件和映射文件中的信息,即載入配置文件和映射文件,並通過Hibernate配置文件生成一個多線程的SessionFactory對象。
然後,多線程SessionFactory對象生成一個線程Session 對象;Session對象生成Query對象或者Transaction對象;可通過Session對象的get(),load(),save(),update(),delete()和saveOrUpdate( )等方法對PO進行載入、保存、更新、刪除等操作。
在查詢的情況下,可通過Session 對象生成一個Query對象,然後利用Query對象執行查詢操作;如果沒有異常,Transaction對象將提交這些操作結果到資料庫中。
(3)多線程hibernate訪問資料庫擴展閱讀:
Hibernate它對JDBC進行了非常輕量級的對象封裝,它將POJO與資料庫表建立映射關系,是一個全自動的orm框架,hibernate可以自動生成SQL語句,自動執行,使得Java程序員可以隨心所欲的使用對象編程思維來操縱資料庫。
Hibernate可以應用在任何使用JDBC的場合,既可以在Java的客戶端程序使用,也可以在Servlet/JSP的Web應用中使用,最具革命意義的是,Hibernate可以在應用EJB的JaveEE架構中取代CMP,完成數據持久化的重任。
⑷ 用org.springframework.orm.hibernate3.HibernateTemplate操作資料庫用不用考慮線程安全問題
並不一定完全線程安全,要看對象創建的類型,比如SessionFactory在方法中創建Session,並返回給調用端,當然不存在線程問題,能保證為不同地點,不同線程的調用者提供不同的Session,而Session一旦創建,就要看調用者如何使用了,把它當做類變數使用,而又把這個類的實例供多個線程操作,而又不加排它鎖,當然會出線程安全的問題。
⑸ java Hibernate的session多線程問題
ThreadLocal類是一個全局共享Map,裡面用於存放每一個線程的唯一實例。
比如你放一個A類進去後,只要還是你這個線程來讀取的話,那麼這個A類還是你存進去的那個A。
而如果是別的線程來讀則是讀不到的,因為他的線程號和你是不同滴。
同理,如果你再放一個A類進去就會覆蓋你以前存入的A類。
在你的程序中,你首先聲明一個final的靜態ThreadLocal,是為了讓此ThreadLocal成為伺服器中唯一的一個實例,然後在這個實例中進行操作:
首先就是查找這個實例中是否存在session,用的是s.get()方法,如果你以前存過的話,會給你以前存儲過的那個session。相反,如果你沒有存過,那麼他會將一個null值傳遞給你程序中的那個sssion。
這時來一個判斷,如果是空,那麼就將你程序中的session進行初始賦值,並將他放入到ThreadLocal中,以便於下次直接調用。
⑹ java線程中調用資料庫查詢就會報空指針異常
如果資料庫的訪問用了Hibernate等框架,建議使用Spring管理該框架,線程啟動時,啟動spring。
如果沒有使用框架,這就是配置或者編碼的問題了
⑺ sqlssessionfactorybuiilder是單線程還是多線程
PO(Persistent Object)只有在session的管理下才可完成資料庫的訪問,為了使用hibernate進行持久化操作,通常
有如下操作:
1.開發持久化類,由POJO載入映射文件組成
2.獲取Configuration
3.獲取SessionFactory
4.獲取Session,打開事務
5.用面向對象的方式操作資料庫
6.關閉事務,關閉session
對PO的操作必須在Session管理下才能同步到資料庫,Session由SessionFactory工廠產生,SessionFactory是由數
據編譯後的內存鏡像,通常一個應用對應一個SessionFactory對象。SessionFactory對象由Configuration對象生成
,Configuration對象負責載入配置文件。
SessionFactory:這是hibernate的關鍵對象,它是單個資料庫映射關系經過編譯後的內存鏡像,它也是線程安全的
。它是生成Session的工廠,本身需要依賴於ConnectionProvider。該對象可以在進程或集群的級別上,為那些事務
之間可以重用的數據提供可選的二級緩存。
Session:它是應用程序與持久儲存層之間交互操作的一個單線程對象。它也是Hibernate持久化操作的關鍵對象,
所有持久化對象必須在Session管理下才可以進行持久化操作。此對象生存周期較短。它底層封裝了JDBC連接,它也
是Transaction的工廠。Session對象持有一個必選的一級緩存,顯式執行flush之前,所有持久化操作的數據都在緩
存中Session處。
持久化對象:系統創建的POJO實例,一旦與特定的Session關聯,並對應數據表的指定記錄,該對象就處於持久化狀
態,這一系列對象都被稱為持久化對象。在程序中對持久化對象執行的修改,都將自動被轉換為持久層的修改。持
久化對象完全可以是普通的JavaBean/POJO,唯一特殊的是他們正與一個Session關聯。
事務(Transaction):代表一次原子操作,它具有資料庫事務的概念。Hibernate事務是對底層具體的JDBC、JTA以
及CORBA事務的抽象。在某些情況下,一個Session之內可能包含多個Transaction對象。雖然事務操作是可選的,但
是所有的持久化操作都應該在事務管理下進行,即使是只讀操作。
⑻ 關於一個資料庫連接問題,我啟動多個線程執行同一條查詢語句,會報"Too many connections"
一般這種是因為超出資料庫最大鏈接上限。再建立鏈接,不管緩存多少,會自動隊列消息等待。Timeout時間內沒有鏈接取消無法獲得鏈接許可權。可以將自己的資料庫鏈接個數設置大一些。
⑼ hibernate多線程操作資料庫的一張表,如何避
多線程是很容易造成死鎖,一般情況下死鎖都是因為並發操作引起的。我不懂JAVA,但死鎖這個問題每種開發工具和資料庫都會碰到.解決辦法是:
1、程序方面優化演算法(如有序資源分配法、銀行演算法等),在一個程序里,能不用多線程更新同一張資料庫表 盡量不要用,如果要用,其避免死鎖的演算法就很復雜。
2、資料庫方面設置等待超時時間
3、發生死鎖後直接KILL掉資料庫進程
⑽ 並發編程解惑之線程
主要內容:
進程是資源分配的最小單位,每個進程都有獨立的代碼和數據空間,一個進程包含 1 到 n 個線程。線程是 CPU 調度的最小單位,每個線程有獨立的運行棧和程序計數器,線程切換開銷小。
Java 程序總是從主類的 main 方法開始執行,main 方法就是 Java 程序默認的主線程,而在 main 方法中再創建的線程就是其他線程。在 Java 中,每次程序啟動至少啟動 2 個線程。一個是 main 線程,一個是垃圾收集線程。每次使用 Java 命令啟動一個 Java 程序,就相當於啟動一個 JVM 實例,而每個 JVM 實例就是在操作系統中啟動的一個進程。
多線程可以通過繼承或實現介面的方式創建。
Thread 類是 JDK 中定義的用於控制線程對象的類,該類中封裝了線程執行體 run() 方法。需要強調的一點是,線程執行先後與創建順序無關。
通過 Runnable 方式創建線程相比通過繼承 Thread 類創建線程的優勢是避免了單繼承的局限性。若一個 boy 類繼承了 person 類,boy 類就無法通過繼承 Thread 類的方式來實現多線程。
使用 Runnable 介面創建線程的過程:先是創建對象實例 MyRunnable,然後將對象 My Runnable 作為 Thread 構造方法的入參,來構造出線程。對於 new Thread(Runnable target) 創建的使用同一入參目標對象的線程,可以共享該入參目標對象 MyRunnable 的成員變數和方法,但 run() 方法中的局部變數相互獨立,互不幹擾。
上面代碼是 new 了三個不同的 My Runnable 對象,如果只想使用同一個對象,可以只 new 一個 MyRunnable 對象給三個 new Thread 使用。
實現 Runnable 介面比繼承 Thread 類所具有的優勢:
線程有新建、可運行、阻塞、等待、定時等待、死亡 6 種狀態。一個具有生命的線程,總是處於這 6 種狀態之一。 每個線程可以獨立於其他線程運行,也可和其他線程協同運行。線程被創建後,調用 start() 方法啟動線程,該線程便從新建態進入就緒狀態。
NEW 狀態(新建狀態) 實例化一個線程之後,並且這個線程沒有開始執行,這個時候的狀態就是 NEW 狀態:
RUNNABLE 狀態(就緒狀態):
阻塞狀態有 3 種:
如果一個線程調用了一個對象的 wait 方法, 那麼這個線程就會處於等待狀態(waiting 狀態)直到另外一個線程調用這個對象的 notify 或者 notifyAll 方法後才會解除這個狀態。
run() 里的代碼執行完畢後,線程進入終結狀態(TERMINATED 狀態)。
線程狀態有 6 種:新建、可運行、阻塞、等待、定時等待、死亡。
我們看下 join 方法的使用:
運行結果:
我們來看下 yield 方法的使用:
運行結果:
線程與線程之間是無法直接通信的,A 線程無法直接通知 B 線程,Java 中線程之間交換信息是通過共享的內存來實現的,控制共享資源的讀寫的訪問,使得多個線程輪流執行對共享數據的操作,線程之間通信是通過對共享資源上鎖或釋放鎖來實現的。線程排隊輪流執行共享資源,這稱為線程的同步。
Java 提供了很多同步操作(也就是線程間的通信方式),同步可使用 synchronized 關鍵字、Object 類的 wait/notifyAll 方法、ReentrantLock 鎖、無鎖同步 CAS 等方式來實現。
ReentrantLock 是 JDK 內置的一個鎖對象,用於線程同步(線程通信),需要用戶手動釋放鎖。
運行結果:
這表明同一時間段只能有 1 個線程執行 work 方法,因為 work 方法里的代碼需要獲取到鎖才能執行,這就實現了多個線程間的通信,線程 0 獲取鎖,先執行,線程 1 等待,線程 0 釋放鎖,線程 1 繼續執行。
synchronized 是一種語法級別的同步方式,稱為內置鎖。該鎖會在代碼執行完畢後由 JVM 釋放。
輸出結果跟 ReentrantLock 一樣。
Java 中的 Object 類默認是所有類的父類,該類擁有 wait、 notify、notifyAll 方法,其他對象會自動繼承 Object 類,可調用 Object 類的這些方法實現線程間的通信。
除了可以通過鎖的方式來實現通信,還可通過無鎖的方式來實現,無鎖同 CAS(Compare-and-Swap,比較和交換)的實現,需要有 3 個操作數:內存地址 V,舊的預期值 A,即將要更新的目標值 B,當且僅當內存地址 V 的值與預期值 A 相等時,將內存地址 V 的值修改為目標值 B,否則就什麼都不做。
我們通過計算器的案例來演示無鎖同步 CAS 的實現方式,非線程安全的計數方式如下:
線程安全的計數方式如下:
運行結果:
線程安全累加的結果才是正確的,非線程安全會出現少計算值的情況。JDK 1.5 開始,並發包里提供了原子操作的類,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 還提供了用原子方式將當前值自增 1 或自減 1 的方法,在多線程程序中,諸如 ++i 或 i++ 等運算不具有原子性,是不安全的線程操作之一。 通常我們使用 synchronized 將該操作變成一個原子操作,但 JVM 為此種操作提供了原子操作的同步類 Atomic,使用 AtomicInteger 做自增運算的性能是 ReentantLock 的好幾倍。
上面我們都是使用底層的方式實現線程間的通信的,但在實際的開發中,我們應該盡量遠離底層結構,使用封裝好的 API,例如 J.U.C 包(java.util.concurrent,又稱並發包)下的工具類 CountDownLath、CyclicBarrier、Semaphore,來實現線程通信,協調線程執行。
CountDownLatch 能夠實現線程之間的等待,CountDownLatch 用於某一個線程等待若干個其他線程執行完任務之後,它才開始執行。
CountDownLatch 類只提供了一個構造器:
CountDownLatch 類中常用的 3 個方法:
運行結果:
CyclicBarrier 字面意思循環柵欄,通過它可以讓一組線程等待至某個狀態之後再全部同時執行。當所有等待線程都被釋放以後,CyclicBarrier 可以被重復使用,所以有循環之意。
相比 CountDownLatch,CyclicBarrier 可以被循環使用,而且如果遇到線程中斷等情況時,可以利用 reset() 方法,重置計數器,CyclicBarrier 會比 CountDownLatch 更加靈活。
CyclicBarrier 提供 2 個構造器:
上面的方法中,參數 parties 指讓多少個線程或者任務等待至 barrier 狀態;參數 barrierAction 為當這些線程都達到 barrier 狀態時會執行的內容。
CyclicBarrier 中最重要的方法 await 方法,它有 2 個重載版本。下面方法用來掛起當前線程,直至所有線程都到達 barrier 狀態再同時執行後續任務。
而下面的方法則是讓這些線程等待至一定的時間,如果還有線程沒有到達 barrier 狀態就直接讓到達 barrier 的線程執行任務。
運行結果:
CyclicBarrier 用於一組線程互相等待至某個狀態,然後這一組線程再同時執行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。
Semaphore 類是一個計數信號量,它可以設定一個閾值,多個線程競爭獲取許可信號,執行完任務後歸還,超過閾值後,線程申請許可信號時將會被阻塞。Semaphore 可以用來 構建對象池,資源池,比如資料庫連接池。
假如在伺服器上運行著若干個客戶端請求的線程。這些線程需要連接到同一資料庫,但任一時刻只能獲得一定數目的資料庫連接。要怎樣才能夠有效地將這些固定數目的資料庫連接分配給大量的線程呢?
給方法加同步鎖,保證同一時刻只能有一個線程去調用此方法,其他所有線程排隊等待,但若有 10 個資料庫連接,也只有一個能被使用,效率太低。另外一種方法,使用信號量,讓信號量許可與資料庫可用連接數為相同數量,10 個資料庫連接都能被使用,大大提高性能。
上面三個工具類是 J.U.C 包的核心類,J.U.C 包的全景圖就比較復雜了:
J.U.C 包(java.util.concurrent)中的高層類(Lock、同步器、阻塞隊列、Executor、並發容器)依賴基礎類(AQS、非阻塞數據結構、原子變數類),而基礎類是通過 CAS 和 volatile 來實現的。我們盡量使用頂層的類,避免使用基礎類 CAS 和 volatile 來協調線程的執行。J.U.C 包其他的內容,在其他的篇章會有相應的講解。
Future 是一種非同步執行的設計模式,類似 ajax 非同步請求,不需要同步等待返回結果,可繼續執行代碼。使 Runnable(無返回值不支持上報異常)或 Callable(有返回值支持上報異常)均可開啟線程執行任務。但是如果需要非同步獲取線程的返回結果,就需要通過 Future 來實現了。
Future 是位於 java.util.concurrent 包下的一個介面,Future 介面封裝了取消任務,獲取任務結果的方法。
在 Java 中,一般是通過繼承 Thread 類或者實現 Runnable 介面來創建多線程, Runnable 介面不能返回結果,JDK 1.5 之後,Java 提供了 Callable 介面來封裝子任務,Callable 介面可以獲取返回結果。我們使用線程池提交 Callable 介面任務,將返回 Future 介面添加進 ArrayList 數組,最後遍歷 FutureList,實現非同步獲取返回值。
運行結果:
上面就是非同步線程執行的調用過程,實際開發中用得更多的是使用現成的非同步框架來實現非同步編程,如 RxJava,有興趣的可以繼續去了解,通常非同步框架都是結合遠程 HTTP 調用 Retrofit 框架來使用的,兩者結合起來用,可以避免調用遠程介面時,花費過多的時間在等待介面返回上。
線程封閉是通過本地線程 ThreadLocal 來實現的,ThreadLocal 是線程局部變數(local vari able),它為每個線程都提供一個變數值的副本,每個線程對該變數副本的修改相互不影響。
在 JVM 虛擬機中,堆內存用於存儲共享的數據(實例對象),也就是主內存。Thread Local .set()、ThreadLocal.get() 方法直接在本地內存(工作內存)中寫和讀共享變數的副本,而不需要同步數據,不用像 synchronized 那樣保證數據可見性,修改主內存數據後還要同步更新到工作內存。
Myabatis、hibernate 是通過 threadlocal 來存儲 session 的,每一個線程都維護著一個 session,對線程獨享的資源操作很方便,也避免了線程阻塞。
ThreadLocal 類位於 Thread 線程類內部,我們分析下它的源碼:
ThreadLocal 和 Synchonized 都用於解決多線程並發訪問的問題,訪問多線程共享的資源時,Synchronized 同步機制採用了以時間換空間的方式,提供一份變數讓多個線程排隊訪問,而 ThreadLocal 採用了以空間換時間的方式,提供每個線程一個變數,實現數據隔離。
ThreadLocal 可用於資料庫連接 Connection 對象的隔離,使得每個請求線程都可以復用連接而又相互不影響。
在 Java 裡面,存在強引用、弱引用、軟引用、虛引用。我們主要來了解下強引用和弱引用:
上面 a、b 對實例 A、B 都是強引用
而上面這種情況就不一樣了,即使 b 被置為 null,但是 c 仍然持有對 C 對象實例的引用,而間接的保持著對 b 的強引用,所以 GC 不會回收分配給 b 的空間,導致 b 無法回收也沒有被使用,造成了內存泄漏。這時可以通過 c = null; 來使得 c 被回收,但也可以通過弱引用來達到同樣目的:
從源碼中可以看出 Entry 里的 key 對 ThreadLocal 實例是弱引用:
Entry 里的 key 對 ThreadLocal 實例是弱引用,將 key 值置為 null,堆中的 ThreadLocal 實例是可以被垃圾收集器(GC)回收的。但是 value 卻存在一條從 Current Thread 過來的強引用鏈,只有當當前線程 Current Thread 銷毀時,value 才能被回收。在 threadLocal 被設為 null 以及線程結束之前,Entry 的鍵值對都不會被回收,出現內存泄漏。為了避免泄漏,在 ThreadLocalMap 中的 set/get Entry 方法里,會對 key 為 null 的情況進行判斷,如果為 null 的話,就會對 value 置為 null。也可以通過 ThreadLocal 的 remove 方法(類似加鎖和解鎖,最後 remove 一下,解鎖對象的引用)直接清除,釋放內存空間。
總結來說,利用 ThreadLocal 來訪問共享數據時,JVM 通過設置 ThreadLocalMap 的 Key 為弱引用,來避免內存泄露,同時通過調用 remove、get、set 方法的時候,回收弱引用(Key 為 null 的 Entry)。當使用 static ThreadLocal 的時候(如上面的 Spring 多數據源),static 變數在類未載入的時候,它就已經載入,當線程結束的時候,static 變數不一定會被回收,比起普通成員變數使用的時候才載入,static 的生命周期變長了,若沒有及時回收,容易產生內存泄漏。
使用線程池,可以重用存在的線程,減少對象創建、消亡的開銷,可控制最大並發線程數,避免資源競爭過度,還能實現線程定時執行、單線程執行、固定線程數執行等功能。
Java 把線程的調用封裝成了一個 Executor 介面,Executor 介面中定義了一個 execute 方法,用來提交線程的執行。Executor 介面的子介面是 ExecutorService,負責管理線程的執行。通過 Executors 類的靜態方法可以初始化
ExecutorService 線程池。Executors 類的靜態方法可創建不同類型的線程池:
但是,不建議使用 Executors 去創建線程池,而是通過 ThreadPoolExecutor 的方式,明確給出線程池的參數去創建,規避資源耗盡的風險。
如果使用 Executors 去創建線程池:
最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。通過線程池實現類 ThreadPoolExecutor 可構造出線程池的,構造函數有下面幾個重要的參數:
參數 1:corePoolSize
線程池核心線程數。
參數 2:workQueue
阻塞隊列,用於保存執行任務的線程,有 4 種阻塞隊列可選:
參數 3:maximunPoolSize
線程池最大線程數。如果阻塞隊列滿了(有界的阻塞隊列),來了一個新的任務,若線程池當前線程數小於最大線程數,則創建新的線程執行任務,否則交給飽和策略處理。如果是無界隊列就不存在這種情況,任務都在無界隊列里存儲著。
參數 4:RejectedExecutionHandler
拒絕策略,當隊列滿了,而且線程達到了最大線程數後,對新任務採取的處理策略。
有 4 種策略可選:
最後,還可以自定義處理策略。
參數 5:ThreadFactory
創建線程的工廠。
參數 6:keeyAliveTime
線程沒有任務執行時最多保持多久時間終止。當線程池中的線程數大於 corePoolSize 時,線程池中所有線程中的某一個線程的空閑時間若達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但如果調用了 allowCoreThread TimeOut(boolean value) 方法,線程池中的線程數就算不超過 corePoolSize,keepAlive Time 參數也會起作用,直到線程池中的線程數量變為 0。
參數 7:TimeUnit
配合第 6 個參數使用,表示存活時間的時間單位最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。
運行結果:
線程池創建線程時,會將線程封裝成工作線程 Worker,Worker 在執行完任務後,還會不斷的去獲取隊列里的任務來執行。Worker 的加鎖解鎖機制是繼承 AQS 實現的。
我們來看下 Worker 線程的運行過程:
總結來說,如果當前運行的線程數小於 corePoolSize 線程數,則獲取全局鎖,然後創建新的線程來執行任務如果運行的線程數大於等於 corePoolSize 線程數,則將任務加入阻塞隊列 BlockingQueue 如果阻塞隊列已滿,無法將任務加入 BlockingQueue,則獲取全局所,再創建新的線程來執行任務
如果新創建線程後使得線程數超過了 maximumPoolSize 線程數,則調用 Rejected ExecutionHandler.rejectedExecution() 方法根據對應的拒絕策略處理任務。
CPU 密集型任務,線程執行任務佔用 CPU 時間會比較長,應該配置相對少的線程數,避免過度爭搶資源,可配置 N 個 CPU+1 個線程的線程池;但 IO 密集型任務則由於需要等待 IO 操作,線程經常處於等待狀態,應該配置相對多的線程如 2*N 個 CPU 個線程,A 線程阻塞後,B 線程能馬上執行,線程多競爭激烈,能飽和的執行任務。線程提交 SQL 後等待資料庫返回結果時間較長的情況,CPU 空閑會較多,線程數應設置大些,讓更多線程爭取 CPU 的調度。