文章目錄
synchronized關鍵字
對某個對象進行加鎖
使用synchronized關鍵字有以下三種使用方式:
- 同步代碼塊
同步代碼塊鎖的是括號裏面的配置對象 - 同步方法
同步方法鎖的是當前實例對象 - 靜態同步方法
靜態同步方法鎖的是當前類的Class對象
- 自己新建一個對象作爲鎖住的對象
public class T{
private int count = 10;
private Object o =new Object();
public void m(){
synchronized(o){
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
}
- 使用this對象作爲鎖對象
public class T{
private int count = 10;
public void m(){
synchronized(this){//同步代碼塊
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
}
- 上文代碼的另外一種寫法
public class T{
private int count = 10;
public synchronized void m(){//同步方法
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
- 靜態同步方法:沒有this對象的靜態方法如何獲得鎖對象
public class T{
private int count = 10;
public synchronized void m(){
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
public static void mm(){
synchronized(T.class){//靜態的方法沒有this對象,所以用反射來獲取它的鎖對象
count--;
}
}
}
public class T implements Runnable{
private int count = 10;
public /*synchronized*/ void run(){//synchronized所標註的代碼塊具備原子性,執行期間不可再分
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
public void main(){
T t = new T();//只有一個對象
for(int i = 0; i < 5; i++){
new Thread(t, "THREAD" + i).start();//開啓五個線程,共同訪問t對象,共同訪問count
}
}
}
同步方法和非同步方法是否可以同時被調用
在一個同步方法執行的過程中,非同步的方法可以運行,因爲非同步方法的執行不需要鎖
public class demo6 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + "m1 start.... ");
try {
Thread.sleep(10000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1 end.... ");
}
public void m2() {
try {
Thread.sleep(5000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 .... ");
}
public static void main(String[] args) {
demo6 t = new demo6();
new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();
}
}
髒讀
寫時需要加鎖,讀時其實也需要加鎖,否則容易出現髒讀的現象
/*寫的時候加鎖
* 讀的時候不加鎖
* 髒讀的現象
*/
package concurrent1;
import java.util.concurrent.TimeUnit;
public class demo8 {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
this.balance = balance;
}
public double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
demo8 aDemo8 = new demo8();
new Thread(()->aDemo8.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(aDemo8.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(aDemo8.getBalance("zhangsan"));
}
}
可重入
import java.util.concurrent.TimeUnit;
/*
* 一個同步方法是否可以調用另外一個同步的方法
* 一個已經擁有某個對象鎖的線程,再次申請的時候仍然會得到該對象的鎖
* 即:synchronized獲得的鎖是可以重入的
*/
public class demo9 {
synchronized void m1() {
System.out.println("m1 start...");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
m2();
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("m2");
}
}
- 子類中調用父類的同步方法
//子類中調用父類的同步方法
import java.util.concurrent.TimeUnit;
public class demo10 {
synchronized void m() {
System.out.println("m start ..");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
}
class TT extends demo10{
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
程序在執行過程中出現異常,鎖會被釋放
import java.sql.Time;
import java.util.concurrent.TimeUnit;
/*
* 程序在執行時,如果出現異常,默認情況下鎖會被釋放,這時其他的線程訪問的數據可能會出現不一致的情況
*/
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start ");
while(true) {
count++;
System.out.println(Thread.currentThread().getName() + "count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
if(count == 5) {
int i = 1/0;
}
}
}
public static void main(String[] args) {
T t =new T();
Runnable r = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
t.m();
}
};
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
volatile 關鍵字:內存可見性
使一個變量在多個線程間可見。
Java編程語言允許線程訪問共享變量,爲了確保共享變量能夠準確一致地更新,線程確保通過排他鎖單獨獲得這個變量。
Java中的volatile關鍵字就是這個定義的體現。如果一個變量被聲明爲volatile,那麼確保這個變量是“可見的”。可見性的意思是當線程修改一個共享變量的時候,另外一個線程能夠讀到這個修改的值。
具體原理參見:這個
例子:
public class T {
volatile boolean running = true;
void m() {
System.out.println(" m start ");
while(running) {
}
System.out.println("m end ...");
}
public static void main(String[] args) {
T t =new T();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
t.running = false;
}
}
volatile 與synchronized的區別:
volatile只能保證可見性
synchronized既保證可見性又保證了原子性
public class T {
volatile int count = 0;//只保證可見性
void m() {
for(int i = 0; i < 10000; i++) count++;
}
public static void main(String[] args) {
T t =new T();
List<Thread> threads = new ArrayList<Thread>();
for(int i = 0; i < 10; i++)
threads.add(new Thread(t::m, "thread-" + i));
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
使用Atomic***類來保證原子性
public class T {
AtomicInteger count = new AtomicInteger(0);
void m() {
for(int i = 0; i < 10000; i++){
// if(count.get() < 1000) //使用Atomicxxx的多個方法並不具備原子性
count.incrementAndGet();//用於替代count++的
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
synchronized的優化
public class T {
int count = 0;
synchronized void m1() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
count++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//業務邏輯中只有下面的這句話需要同步,這時不應該在給整個方法加上鎖
//採用更加細粒度的方法,可以使線程爭用的時間變短,提高效率
synchronized (this) {
count++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
鎖住的對象屬性發生改變不會影響鎖的使用,但是如果引用的對象改變情況就大爲不同了
public class T {
Object o = new Object();
void m() {
synchronized (o) {
while(true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
Thread t2 = new Thread(t::m, "t2");
t.o = new Object();
t2.start();
}
}
不應該用字符串作爲鎖定的對象
public class T {
String s1 = "Hello";
String s2 = "Hello";
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
}