可見性-->爲了多個線程之間的數據能夠進行通信, 所以需要提供共享變量來解決
Java內存模型(JMM)規定了jvm有主內存,主內存是多個線程共享的。當new一個對象的時候,也是被分配在主內存中,每個線程都有自己的工作內存,工作內存存儲了主存的某些對象的副本,當然線程的工作內存大小是有限制的。當線程操作某個對象時,執行順序如下:
(1) 從主存複製變量到當前工作內存 (read and load)
(2) 執行代碼,改變共享變量值 (use and assign)
(3) 用工作內存數據刷新主存相關內容 (store and write)
所謂的可見性就是當一個共享變量在多個線程的工作內存中都有副本時,如果一個線程修改了這個共享變量,那麼其他線程應該能夠看到這個被修改後的值
所謂的有序性-->也稱原子操作, 就是事務處理-->多線程同時對一個共享變量進行操作時, 必須等一個a線程完成,並把結果更新到主內存後, b線程纔可以對該變量進行修改
public class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void add(int num) {
balance = balance + num;
}
public void withdraw(int num) {
balance = balance - num;
}
public static void main(String[] args) throws InterruptedException {
Account account = new Account(1000);
Thread a = new Thread(new AddThread(account, 20), "add");
Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
a.start();
b.start();
a.join();
b.join();
System.out.println(account.getBalance());
}
static class AddThread implements Runnable {
Account account;
int amount;
public AddThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
for (int i = 0; i < 200000; i++) {
account.add(amount);
}
}
}
static class WithdrawThread implements Runnable {
Account account;
int amount;
public WithdrawThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
for (int i = 0; i < 100000; i++) {
account.withdraw(amount);
}
}
}
}
在a線程中不斷給account加錢, b線程不斷給account取錢, 因爲線程的執行順序是不可預見的, 所以第一次執行結果爲10200,第二次執行結果爲1060,每次執行的結果都是不確定的(更新到主內存, 只有更新到主內存後才能算是一次完整的操作)
synchronized
爲了解決上面的問題, 可以通過synchronized 解決, 它可以保證線程的內存有序性問題public synchronized void add(int num) {
balance = balance + num;
}
public synchronized void withdraw(int num) {
balance = balance - num;
}
生產者/消費者模式
生產者/消費者模式其實是一種很經典的線程同步模型,很多時候,並不是光保證多個線程對某共享資源操作的互斥性就夠了,往往多個線程之間都是有協作的。假設有這樣一種情況,有一個桌子,桌子上面有一個盤子,盤子裏只能放一顆雞蛋,A專門往盤子裏放雞蛋,如果盤子裏有雞蛋,則一直等到盤子裏沒雞蛋,B專門從盤子裏拿雞蛋,如果盤子裏沒雞蛋,則等待直到盤子裏有雞蛋。其實盤子就是一個互斥區,每次往盤子放雞蛋應該都是互斥的,A的等待其實就是主動放棄鎖,B等待時還要提醒A放雞蛋。
如何讓線程主動釋放鎖
很簡單,調用鎖的wait()方法就好。wait方法是從Object來的,所以任意對象都有這個方法。看這個代碼片段:
import java.util.ArrayList;
import java.util.List;
public class Plate {
List<Object> eggs = new ArrayList<Object>();
public synchronized Object getEgg() {
if (eggs.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
Object egg = eggs.get(0);
eggs.clear();// 清空盤子
notify();// 喚醒阻塞隊列的某線程到就緒隊列
System.out.println("拿到雞蛋");
return egg;
}
public synchronized void putEgg(Object egg) {
if (eggs.size() > 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
eggs.add(egg);// 往盤子裏放雞蛋
notify();// 喚醒阻塞隊列的某線程到就緒隊列
System.out.println("放入雞蛋");
}
static class AddThread extends Thread{
private Plate plate;
private Object egg=new Object();
public AddThread(Plate plate){
this.plate=plate;
}
public void run(){
for(int i=0;i<5;i++){
plate.putEgg(egg);
}
}
}
static class GetThread extends Thread{
private Plate plate;
public GetThread(Plate plate){
this.plate=plate;
}
public void run(){
for(int i=0;i<5;i++){
plate.getEgg();
}
}
}
public static void main(String args[]){
try {
Plate plate=new Plate();
Thread add=new Thread(new AddThread(plate));
Thread get=new Thread(new GetThread(plate));
add.start();
get.start();
add.join();
get.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("測試結束");
}
}
執行結果:
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
測試結束
聲明一個Plate對象爲plate,被線程A和線程B共享,A專門放雞蛋,B專門拿雞蛋。假設
1 開始,A調用plate.putEgg方法,此時eggs.size()爲0,因此順利將雞蛋放到盤子,還執行了notify()方法,喚醒鎖的阻塞隊列的線程,此時阻塞隊列還沒有線程。
2 又有一個A線程對象調用plate.putEgg方法,此時eggs.size()不爲0,調用wait()方法,自己進入了鎖對象的阻塞隊列。
3 此時,來了一個B線程對象,調用plate.getEgg方法,eggs.size()不爲0,順利的拿到了一個雞蛋,還執行了notify()方法,喚醒鎖的阻塞隊列的線程,此時阻塞隊列有一個A線程對象,喚醒後,它進入到就緒隊列,就緒隊列也就它一個,因此馬上得到鎖,開始往盤子裏放雞蛋,此時盤子是空的,因此放雞蛋成功。
4 假設接着來了線程A,就重複2;假設來料線程B,就重複3。
整個過程都保證了放雞蛋,拿雞蛋,放雞蛋,拿雞蛋。
轉自:點擊打開鏈接
servlet中全局變量用來統計訪問數
class Count {
private int count;
public int getCount() {
return count;
}
public synchronized void add() {
this.count += 1;
}
}
也可以通過AtomicInteger提供的接口