1.synchronized 鎖有兩種:
1.類的實例
2.類對象
第一種類對象
public static void test(){
System.out.println(Thread.currentThread().getName()+" start ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end ");
}
public void test2(){
synchronized (getClass()) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" run ");
}
}
public static void main(String[] args) {
TestThread10 t = new TestThread10();
new Thread(()->TestThread10.test(),"線程 1 ").start();
new Thread(()->t.test2(),"線程 2 ").start();
}
其結果如下:
在線程1,也就是先啓動線程1且等線程1走完,才執行線程2
第二種類的實例
那就是兩個不同的鎖,不會干擾
public void test2(){
synchronized (this) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" run ");
}
}
2.使用synchronized的時候,出現異常一定要處理,不然他會自動釋放鎖
它的機制是手動加鎖,自動釋放鎖。下面看一個例子,在異常的地方一定要處理異常,不然就會想下面代碼中的線程1,會被釋放掉。
private Integer c = 0;
@Override
public void run() {
count();
}
private synchronized void count(){
System.out.println(Thread.currentThread().getName()+" start。。。");
while (true) {
System.out.println(Thread.currentThread().getName()+" count="+c++);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (c == 5) {
int i = 1/0;
}
}
}
public static void main(String[] args) {
TestThread3 t = new TestThread3();
Thread t1 = new Thread(t, "線程 1");
Thread t2 = new Thread(t, "線程 2");
t1.start();
t2.start();
}
3.主線程和子線程
線程分用戶線程和守護線程,main方法其實是一個主線程,在操作系統啓動java.exe後,是開啓了一個進程,然後進程啓動main線程,main線程有啓動其他線程。
但是子線程不受主線程影響,當主線程結束後,其子線程任然在運行,就像上面代碼所執行的結果一樣,主線程啓動完子線程後就結束了,但啓動的兩個子線程都是非守護線程,即不受主線程影響;所以當子線程1異常結束,線程2任然繼續
設置守護線程,記住,設置守護線程要在start方法之前設置
t2.setDaemon(true);
t2.start();
這裏有兩條原則:
-
主線程結束,用戶線程繼續執行,
-
所有子線程都是守護線程,那麼主線程結束,所有子線程都會結束,如果存在用戶線程,那麼在用戶線程結束後結束
4.volatile的作用
-
防止計算機指令的重排序
-
保證線程間變量的可見性
它不保證原子性,是針對java而實現的功能
看下面代碼,對同一個對象的變量進行自增,結果是100000,貌似很正常
public class TestThread4{
private int c = 0;
private void count(){
for (int i = 0; i < 100000; i++) {
System.out.println("count = "+ ++c);;
}
}
public static void main(String[] args) {
TestThread4 t = new TestThread4();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Thread(t::count,"thread-"+i));
}
list.forEach(a->a.start());
list.forEach(a-> {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("main end");
}
}
我們在c上增加volatile修飾:
private volatile int c = 0;
比如在第一個線程在拿到c後進行自增,同時另一個線程也去拿了c,都同時自增,然後都寫入同樣的值,導致的這樣的結果。
5.notify是隨機啓動等待線程中的一個,並且跟線程優先級無關
notify是隨機啓動等待線程中的一個,並且跟線程優先級無關,且 wait和notify方法要在同一把lock的情況下使用;還有一點是lock.wait 阻塞還後會把鎖讓出給需要的線程,然而,在其他線程執行完後,調用lock.notify(),喚醒等待的線程,但是在當前鎖裏的代碼沒執行完,不會釋放掉鎖。
簡單場景模擬:
一個固定容量同步容器,擁有put和get方法,以及getCount方法,能夠支持兩個生產者線程以及10個消費者線程的阻塞調用。
public class TestThread8 {
private final LinkedList list = new LinkedList();
private final int MAX = 10;
private int count = 0;
public synchronized void put(Object o) {
while (list.size() == MAX) {
try {
// 在這裏等待;的那個調用notify時會從這裏繼續執行
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(o);
count++;
// 啓動所有線程,包括生產者,隨機的
this.notifyAll();
}
public synchronized void get() {
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.removeLast();
count--;
this.notifyAll();
}
public int getCount() {
return list.size();
}
public static void main(String[] args) {
TestThread8 t = new TestThread8();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
int j = 0;
while (true) {
t.put(Thread.currentThread().getName() + " put " + t.getCount());
System.out.println(Thread.currentThread().getName() + " put " + t.getCount());
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
t.get();
System.out.println(Thread.currentThread().getName() + " get " + t.getCount());
}
}).start();
}
}
}