Java Concurrency程式碼例項之三原子變數
1. 前言
按照用途與特性,Concurrency包中包含的工具被分為六類(外加一個工具類TimeUnit),即:
1. 執行者與執行緒池
2. 併發佇列
3. 同步工具
4. 併發集合
5. 鎖
6. 原子變數
本文介紹的是其中的原子變數,為什麼調整介紹的順序,是因為在寫前兩篇的時候意識到非阻塞併發的基礎是CAS(CompareAndSwap,比較並替換,後面會詳細介紹),而CAS的基礎是Unsafe類,因此最好先找一個地方系統性的介紹一下Unsafe和CAS,這個地方就是原子變數類。
2. Java的指標Unsafe類
Java放棄了指標,獲得了更高的安全性和記憶體自動清理的能力。但是,它還是在一個角落裡提供了類似於指標的功能,那就是sun.misc.Unsafe類,利用這個類,可以完成許多需要指標才能提供的功能,例如構造一個物件,但是不呼叫建構函式;找到物件中一個變數的地址,然後直接給它賦值,無視其final屬性;通過地址直接運算元組;或者是進行CAS操作。例子如下:
public class UnSafeExam {
public static void main(String[] args) throws InstantiationException, NoSuchFieldException {
//獲得一個UnSafe例項
Unsafe unsafe = null;
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (unsafe != null) {
try {
//構造一個物件,且不呼叫其建構函式
Test test = (Test) unsafe .allocateInstance(Test.class);
//得到一個物件內部屬性的地址
long x_addr = unsafe.objectFieldOffset(Test.class.getDeclaredField("x"));
//直接給此屬性賦值
unsafe.getAndSetInt(test, x_addr, 47);
System.out.println(test.getX());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
//通過地址運算元組
if (unsafe != null) {
final int INT_BYTES = 4;
int[] data = new int[10];
System.out.println(Arrays.toString(data));
long arrayBaseOffset = unsafe.arrayBaseOffset(int[].class);
System.out.println("Array address is :" + arrayBaseOffset);
unsafe.putInt(data, arrayBaseOffset, 47);
unsafe.putInt(data, arrayBaseOffset + INT_BYTES * 8, 43);
System.out.println(Arrays.toString(data));
}
//CAS
if (unsafe != null) {
Test test = (Test) unsafe.allocateInstance(Test.class);
long x_addr = unsafe.objectFieldOffset(Test.class.getDeclaredField("x"));
unsafe.getAndSetInt(test, x_addr, 47);
unsafe.compareAndSwapInt(test, x_addr, 47, 78);
System.out.println("After CAS:" + test.getX());
}
}
static class Test {
private final int x;
Test(int x) {
this.x = x;
System.out.println("Test ctor");
}
int getX() {
return x;
}
}
}
熟悉反射的人應該很快能夠理解上面的程式碼,下面重點說說CAS這個操作。CAS即CompareAndSwap操作,在Unsafe中它有如下形式:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
這三個方法都有四個引數,其中第一和第二個引數代表物件的例項以及地址,第三個引數代表期望值,第四個引數代表更新值。CAS的語義是,若期望值等於物件地址儲存的值,則用更新值來替換物件地址儲存的值,並返回true,否則不進行替換,返回false。
後面我們會看到諸多的原子變數,例如AtomicInteger、AtomicLong、AtomicReference等等都提供了CAS操作,其底層都是呼叫了Unsafe的CAS,它們的引數往往是三個,物件值、期望值和更新值,其語義也與Unsafe中的一致。
CAS是所有原子變數的原子性的基礎,為什麼一個看起來如此不自然的操作卻如此重要呢?其原因就在於這個native操作會最終演化為一條CPU指令cmpxchg,而不是多條CPU指令。由於CAS僅僅是一條指令,因此它不會被多執行緒的排程所打斷,所以能夠保證CAS操作是一個原子操作。補充一點,當代的很多CPU種類都支援cmpxchg操作,但不是所有CPU都支援,對於不支援的CPU,會自動加鎖來保證其操作不會被打斷。
由此可知,原子變數提供的原子性來自CAS操作,CAS來自Unsafe,然後由CPU的cmpxchg指令來保證。
3. i++不是執行緒安全的
所謂“執行緒安全的”,意思是在多執行緒的環境下,多次執行,其結果是不變的,或者說其結果是可預知的。若某些對變數的操作不能保持原子性,則其操作就不是執行緒安全的。
為了說明原子性,來給出一個沒有實現原子性的例子,例如i++這一條語句,它實際上會被編譯為兩條CPU指令,因此若一些執行緒在執行時被從中打斷,就會造成不確定的後果,如下:
public class IplusplusExam {
private volatile static int i = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
for (int j = 0; j < 10; j++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 10000; k++) {
i++;
}
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println(i);
}
}
十個執行緒分別對i變數進行10000次i++操作,若i++是執行緒安全的,則最終i應該等於100000,但是你會發現每次結果都不一樣。
4. 保持原子性的AtomicInteger
若要保持一個變數改變數值時的原子性,目前Java最簡單的方法就是使用相應的原子變數,例如AtomicInteger、AtomicBoolean和AtomicLong。再來看一個例子:
public class AtomicIntegerExam {
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger(0);
ExecutorService service = Executors.newCachedThreadPool();
for (int j = 0; j < 10; j++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 10000; k++) {
atomicInteger.incrementAndGet();
}
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println(atomicInteger.get());
}
}
這次的結果為保持為100000了。因為AtomicInteger的incrementAndGet()操作是原子性的。觀察其內部程式碼,它使用了Unsafe的compareAndSwapInt()方法。
那麼現在整形有AtomicInteger,長整型有AtomicLong,布林型有AtomicBoolean,那麼浮點型怎麼辦?JDK的說法是程式設計師可以利用AtomicInteger以及Float.floatToRawIntBits和Float.intBitsToFloat來自己實現一個AtomicFloat;利用AtomicLong以及Double.doubleToRawLongBits和Double.longBitsToDouble來自己實現一個AtomicDouble。在網上可以搜尋到相應的實現https://my.oschina.net/apdplat/blog/418019,這裡就不再贅述了。
5. 原子引用AtomicReference
Java的變數有兩種型別,原始型別和引用型別。上一章講了原始型別對應的原子變數,這一章講的便是原子引用AtomicReference,它的作用就是能夠實現對引用型別的原子化更改。例子如下:
public class AtomReferenceExam {
public static void main(String[] args) throws InterruptedException {
AtomicReference<Element> reference = new AtomicReference<>(new Element(0, 0));
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
boolean flag = false;
while (!flag) {
Element storedElement = reference.get();
Element newElement = new Element(storedElement.x + 1, storedElement.y + 1);
flag = reference.compareAndSet(storedElement, newElement);
}
}
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println("element.x=" + reference.get().x + ",element.y=" + reference.get().y);
}
private static class Element {
int x;
int y;
public Element(int x, int y) {
this.x = x;
this.y = y;
}
}
}
值得注意的有兩點,一是如果有好幾個變數要同時進行原子化的改變,那麼可以把這幾個變數放到一個Java類中,做成一個所謂的POJO(Plain Ordinary Java Object)類,然後使用AtomicReference來操作這個類。
第二點是以下這段程式碼:
boolean flag = false;
while (!flag) {
Element storedElement = reference.get();
Element newElement = new Element(storedElement.x + 1, storedElement.y + 1);
flag = reference.compareAndSet(storedElement, newElement);
}
這是一種很通用的寫法,在很多情況下,這種類似的寫法都被稱之為自旋鎖(spinLock,我們會在後續的章節中介紹)。在使用AtomicReference的時候,會常常使用這種寫法。
6. AtomicIntegerFieldUpdater
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater都被稱為原子屬性更新器。這些類的應用場景是:如果已經有一個寫好的類,但是隨著業務場景的變化,其中某些屬性在寫入的時候需要保持原子性,那麼就可以使用以上的類來實現這種原子性,並保持類的原有介面不變。
例子如下:
public class AtomicIntegerFieldUpdaterExam {
public static void main(String[] args) throws InterruptedException {
Student student = new Student(0, "Alex Wang");
AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "id");
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
updater.getAndIncrement(student);
}
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println(student);
}
private static class Student {
volatile int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student id = " + id + ",name = " + name;
}
}
}
上面的例子中給出了一個原有的類Student,其中屬性id是volatile int(注意,要應用原子屬性更新器的屬性必須是volatile的),為了使這個屬效能夠被原子化的改變,我們建立了一個AtomicIntegerFieldUpdater,其構造方法為AtomicIntegerFieldUpdater.newUpdater(Student.class, “id”),注意第一個引數是一個class,而第二個引數是屬性名字的字串值(這裡顯然用到了反射)。接下來就可以使用這個Updater來更新屬性值了,其用法類似於AtomicInteger。10個執行緒分別對這個屬性進行了10000次加1操作,結果為100000。
7. AtomicIntegerArray原子陣列
AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray是原子陣列,陣列中每個元素在改變時都可以保持原子性。例子如下:
public class AtomicIntegerArrayExam {
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray array = new AtomicIntegerArray(5);
array.set(0, 0);
array.set(1, 0);
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
array.getAndIncrement(0);
}
for (int j = 0; j < 10000; j++) {
array.getAndIncrement(1);
}
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println("array[0] = "+array.get(0)+", array[1] = "+array.get(1));
}
}
10個執行緒分別對array[0]和array[1]進行了10000次加1操作,結果符合原子性。還有一點值得注意的是,為了效能考慮,應該儘量使用AtomicIntegerArray[n],而不是AtomicInteger[n],因為後者需要建立n個原子變數例項,而前者只需要建立一個原子變數陣列例項,而完成的功能是一樣的。
8. AtomicStampedReference帶有版本號的原子引用
AtomicStampedReference和AtomicMarkableReference是atomic包中兩個比較難以理解的類,它們都是為了解決ABA問題而創建出來的。
8.1 ABA問題
在介紹AtomicReference的時候已經說過,為了實現原子引用的原子性改變,需要用一種類似於自旋鎖的程式碼寫法,如下:
boolean flag = false;
while (!flag) {
Element oldValue = reference.get();
Element newValue = new Element(…);
//如果有其他執行緒在這裡將oldValue從A改為B,做了一些事情,然後又將oldValue改為A,則下面的語句依然能夠返回true
flag = reference.compareAndSet(oldValue, newValue);
}
以上情況下,oldValue從A改為B,又從B改為A,不會影響compareAndSet的返回值。但是在某些情況下,會造成不確定的結果,因此影響了執行緒安全性,這種問題就叫做自旋鎖的ABA問題,例如:
ABA問題一般存在於連結串列、棧這類的併發資料結構中。從上面的例子中可以看出,由於ABA問題,最後的結果是,在特定的條件下,一個ACD棧(三個元素),經過一個pop操作(執行緒1)變成了B(一個元素),這顯然不是執行緒安全的。
下面的程式碼中,我模擬了這個例子(其中很多地方並未充分考慮併發的正確性,主要是為了展示ABA問題):
public class ABAProblem {
public static void main(String[] args) throws InterruptedException {
MyStack<String> stack = new MyStack<>();
stack.push("B");
stack.push("A");
System.out.println("Stack init:" + stack);
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("Thread1");
stack.pop();
System.out.println("Thread1 pop :" + stack);
}
});
service.execute(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("Thread2");
Node<String> A = stack.pop();
System.out.println("Thread2 pop :" + stack);
stack.pop();
System.out.println("Thread2 pop :" + stack);
stack.push("D");
System.out.println("Thread2 push D:" + stack);
stack.push("C");
System.out.println("Thread2 push C:" + stack);
stack.push(A);
System.out.println("Thread2 push A:" + stack);
}
});
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
System.out.println("Stack result:" + stack);
}
static class MyStack<T> {
AtomicReference<Node<T>> head = new AtomicReference<>(null);
public void push(T value) {
Node<T> node = new Node<>(value);
push(node);
}
public void push(Node<T> node) {
for (; ; ) {
Node<T> tmpHead = head.get();
if (head.compareAndSet(tmpHead, node)) {
node.setNext(tmpHead);
return;
}
}
}
public Node<T> pop() {
for (; ; ) {
Node<T> node = head.get();
if (node == null) {
return null;
}
Node<T> nextNode = node.getNext();
// add this sleep to cause ABA problem
if (Thread.currentThread().getName().equals("Thread1")) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (head.compareAndSet(node, nextNode)) {
return node;
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
Node<T> node = head.get();
while (node != null) {
sb.append(node.getValue());
if (node.getNext() != null) {
sb.append(",");
}
node = node.getNext();
}
sb.append("]");
return sb.toString();
}
}
private static class Node<T> {
private T value;
private Node<T> next;
public Node(T value) {
this.value = value;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
}
執行結果是:
Stack init:[A,B]
Thread2 pop :[B]
Thread2 pop :[]
Thread2 push D:[D]
Thread2 push C:[C,D]
Thread2 push A:[A,C,D]
Thread1 pop :[B]
Stack result:[B]
程式碼中的push和pop方法都用無限迴圈的for語句實現,這也是併發中的常見寫法,與前面類似自旋鎖的while語句實現類似的功能,但由於不需要定義一個boolean變數,因此更加簡潔。為了保證ABA問題一定出現,我特意插入了一個針對特定執行緒的sleep語句。在現實中,出現ABA的機率其實是很小的。
8.2 用AtomicStampedReference解決ABA問題
ABA問題的實質是:在併發程式設計中,僅靠檢查變數的值是無法知道這個變數是否被改動過的,還要加上一個版本號(當變數改變就改變其版本號)才能確定變數保持不變。AtomicStampedReference實現了此功能,它儲存變數引用的同時,還賦予此變數一個版本號。每當變數改動時(這個改動是程式設計師自定義的,例如儲存的數值改變,或者是變數在記憶體中的位置移動了,或者是變數在某一個數據結構中被移動了),AtomicStampedReference可以同時改動版本號;因此在進行CAS操作時,同時檢查引用和版本號,只有同時符合才能成功。如此變可以確保ABA問題不會發生了。
程式碼進行如下改動(僅改動MyStack):
static class MyStack<T> {
//initialStamp = 0
AtomicStampedReference<Node<T>> head = new AtomicStampedReference<>(null, 0);
public void push(T value) {
Node<T> node = new Node<>(value);
push(node);
}
public void push(Node<T> node) {
for (; ; ) {
Node<T> tmpHead = head.getReference();
int stamp = head.getStamp();
if (head.compareAndSet(tmpHead, node, stamp, stamp + 1)) {
node.setNext(tmpHead);
return;
}
}
}
public Node<T> pop() {
for (; ; ) {
Node<T> node = head.getReference();
int stamp = head.getStamp();
if (node == null) {
return null;
}
Node<T> nextNode = node.getNext();
// add this sleep to cause ABA problem
if (Thread.currentThread().getName().equals("Thread1")) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (head.compareAndSet(node, nextNode, stamp, stamp + 1)) {
return node;
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
Node<T> node = head.getReference();
while (node != null) {
sb.append(node.getValue());
if (node.getNext() != null) {
sb.append(",");
}
node = node.getNext();
}
sb.append("]");
return sb.toString();
}
}
在每次改動head儲存的變數時,都同時給版本號加1,這樣就避免了ABA問題的發生,執行結果如下:
Stack init:[A,B]
Thread2 pop :[B]
Thread2 pop :[]
Thread2 push D:[D]
Thread2 push C:[C,D]
Thread2 push A:[A,C,D]
Thread1 pop :[C,D]
Stack result:[C,D]
另外值得一提的是,AtomicStampedReference還有一個簡化版AtomicMarkableReference,它儲存的版本號是一個boolean值,適用於某些簡化的情景下。
9. 小結
所謂“執行緒安全的”,就是在併發環境下能夠保持執行結果不變。除了原始的synchronize阻塞方法外,使用原子性的語句能夠在保持執行緒安全的前提下提供更好的效能。原子性的基礎是CAS語句,而它則是由Unsafe類提供的。有了此利器,JDK提供了AtomicInteger、AtomicBoolean和AtomicLong等類來實現整形、布林型和長整型變數的原子增減操作;提供了AtomicReference來實現引用型別的原子操作;提供了AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater來實現原有類中某個屬性的原子更新操作;提供了AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray等原子陣列,陣列中每個元素在改變時都可以保持原子性。為了避免ABA問題,提供了AtomicStampedReference和AtomicMarkableReference。
相關推薦
Java Concurrency程式碼例項之三原子變數
1. 前言 按照用途與特性,Concurrency包中包含的工具被分為六類(外加一個工具類TimeUnit),即: 1. 執行者與執行緒池 2. 併發佇列 3. 同步工具 4. 併發集合 5. 鎖 6. 原子變數 本文介紹的是
Java NIO程式設計例項之三Selector
Java NIO主要包含三個概念,即緩衝區(Buffer)、通道(Channel)和選擇器(Selector)。前面的文章已經介紹了緩衝區和通道,本文則講述最複雜的選擇器Selector。 本文是本系列的第三篇文章,關於緩衝區Buffer可以看第一篇: ht
SonarQube4.4+Jenkins進行程式碼檢查例項之三-單元測試分析
本文來介紹如何利用SonarQube來分析單元測試。最新推薦在分析外掛是Jacoco。當然要進行單元測試,首先單元測試得到了書寫,能夠本地執行得到結果。本示例採用Maven的典型結構。1,配置Maven
Java基礎知識回顧之三 ----- 封裝、繼承和多態
get flex 防止 應用 需要 當前 nim lex aging 前言 在上一篇中回顧了java的修飾符和String類,這篇就來回顧下Java的三大特性:封裝、繼承、多態。 封裝 什麽是封裝 在面向對象程式設計方法中,封裝是指一種將抽象性函式接口的實現細節部份包裝、
簡單知識點例項之三:Bootstrap-Table和後臺進行百分百互動的簡單例項
這是一個針對前後臺互動的例子,可以直接套進專案中通暢執行。第二頁之所以資料不對,是因為例子畢竟沒有真正的後臺,所以資料不對。但是可以套入專案中,就正常了。 重點: 其中bootstrap-table有一個search:truse搜尋框的引數我沒有使用,而是自己寫了一個搜尋框,
JAVA啟動引數大全之三:非Stable引數
前面我們提到用-XX作為字首的引數列表在jvm中可能是不健壯的,SUN也不推薦使用,後續可能會在沒有通知的情況下就直接取消了;但是由於這些引數中的確有很多是對我們很有用的,比如我們經常會見到的-XX:PermSize、-XX:MaxPermSize等等; 下面我們將就Java Hot
Java多線程之三volatile與等待通知機制示例
不存在 跳出循環 三種 安全 同步 完成後 了解 try code 原子性,可見性與有序性 在多線程中,線程同步的時候一般需要考慮原子性,可見性與有序性 原子性 原子性定義:一個操作或者多個操作在執行過程中要麽全部執行完成,要麽全部都不執行,不存在執行一部分的情況。 以我們
python子程序模組subprocess詳解與應用例項 之三
二、應用例項解析 2.1 subprocess模組的使用 1. subprocess.call subprocess.call([“ls”, “-l”]) 0 subprocess.call(“ex
Java開發程式碼規範之單元測試
文章通過學習《阿里巴巴Java開發手冊》整理 1.好的單元測試必須遵守AIR原則 好的單元測試巨集觀上來說,具有自動化、獨立性、可重複執行的特點。 A: Automatic (自動化) I:Independent (獨立性) R:Repeatable(可重複) 2.單元測試
Java開發程式碼規範之程式設計規約(二)——常量定義
文章通過學習《阿里巴巴Java開發手冊》整理。 1 不允許任何魔法值(即未定義的常量)直接出現在程式碼中。 反例: String key = "Id#taobao_" + tradeId; cache.put(key, value); 2 lon
Java多執行緒之三volatile與等待通知機制示例
原子性,可見性與有序性 在多執行緒中,執行緒同步的時候一般需要考慮原子性,可見性與有序性 原子性 原子性定義:一個操作或者多個操作在執行過程中要麼全部執行完成,要麼全部都不執行,不存在執行一部分的情況。 以我們在Java程式碼中經常用到的自增操作i++為例,i++實際上並不是一步操作,而是首先對i的值加一,然
java開發程式碼規範之我見
最近開開發過程中發現命名方面不規範,程式碼寫了也經常沒有格式化導致增加了程式碼的可讀難度。之前還以為自己程式碼有註釋已經很給力了, 但是最近到了新的工作環境才發現如果能用英文準確的表達我這個方法
【深入Java虛擬機器】之三:類載入機制
類載入過程 類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。它們開始的順序如下圖所示: 其中類載入的過程包括了載入、驗證、準備、
【Java面試題】之三次握手和四次揮手
本文內容大部分轉載自:http://blog.csdn.net/whuslei/article/details/6667471/ 原文獲得了54萬的閱讀量,說明改文章的質量很高 同時,博主在原文的基礎上也補充了一些內容 建立TCP需要三次握手才能建立,而斷開連線則需要
Java開發程式碼規範之MySQL資料庫(一)——建表規約
文章通過學習《阿里巴巴Java開發手冊》整理 1。表達是否概念的欄位,必須使用is_xxx的方式命名,資料型別是unsigned tinyint(1表示是,0表示否) 說明:任何欄位如果為非負數,必須是unsigned。 例子:表達邏輯刪除的欄位名
Java開發程式碼規範之異常日誌(二)——日誌規約
文章通過學習《阿里巴巴Java開發手冊》整理 1。應用中不可直接使用日誌系統(Log4j、Logback)中的API,而應依賴使用日誌框架SLF4J中的API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。 import org.
Java開發的遊戲之三 坦克大戰 完整原始碼
截圖: 程式碼過長不在此貼出,所有的程式碼都打包在以下連結中壓縮包裡,包括背景圖片。 原始碼下載連結: http://download.csdn.net/detail/destiny199
Java多執行緒之物件及變數的併發訪問
Java物件及變數的併發訪問 當多個執行緒同時對同一個物件中的例項變數進行併發訪問時可能會產生執行緒安全問題。產生的後果就是”髒讀”,即收到的資料其實是被更改過的。 如果訪問的是方法中的變數,則不存在”非執行緒安全”問題 可以通過以下幾種方式來解決,在對物
axis2客戶端呼叫免費的webservice服務的例項之三axis2使用RPC方式呼叫網上免費WebService
前面兩節我們分別說了呼叫webservice的兩種方式: 下面我們介紹一下第三種方式:axis2使用RPC方式呼叫網上免費WebService 這種方式類似第二種方式,只是用到的類不同,但是這種方式感覺容易出問題。不多說,直接上程式碼 <span style=
Java與Highcharts例項(三)
上一回,我們使用官方的介紹,完成了Highcharts的入門 1. 引入Highcharts 依賴的JS 2. 新建DIV容器 3. 編寫JS 在這裡,我們用Java做後臺實現資料的傳遞 1. 改造JS 我們為了使用從後臺傳過來的資料,需要對JS做些修改 <scri