public class ReentrantLock5 extends Thread{
private static Lock lock = new ReentrantLock(true);
// true 獲得公平鎖
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + i+" 獲得鎖");
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock5 rl=new ReentrantLock5();
Thread th1=new Thread(rl);
Thread th2=new Thread(rl);
th1.start();
th2.start();
}
}
ReentrantLock還可以指定爲公平鎖什麼是公平鎖,什麼是不公平鎖,假設很多個線程訪問同一份資源的時候都要鎖定, 其中某一個線程A如果拿到了,他佔有這把鎖之後,其他人是都訪問不了的,都得在那等着;什麼時候一旦線程A釋放了這把鎖,那麼剩下的線程中哪個線程得到這把鎖,這事說不定,這個要看線程調度器自己去選哪個了,所以這叫競爭鎖;也就是說等待的線程裏面是沒 有公平性可言的;不過這種效率比較高,線程調度器不用計算到底哪個線程等的時間更長,所以默認的synchronized是非公平鎖; 公平鎖就是,誰等的時間長讓誰得到那把鎖。
面試題:生產者消費者程序:
要求:寫一個固定容量同步容器,擁有put和get方法,以及getCount方法,能夠支持2個生產者線程以及10個消費者線程的阻塞調用
public class ResumeContainer<T> {
private List<T> lists = new ArrayList<>();
final private LinkedList<T> list = new LinkedList<>();
private int max_ = 10;
private int cnt = 0;
public synchronized void put(T t){
while (list.size() == max_){// 對比這裏if 和 while 帶來的區別
try {
System.out.println("list size --------------------: " + list.size());
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
System.out.println("producer put " + t);
cnt++;
this.notifyAll();// 通知消費者前來消費
}
public synchronized T get(){
T t = null;
while (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = list.remove(0);
cnt --;
this.notifyAll();
return t;
}
public static void main(String[] args){
ResumeContainer<Object> container = new ResumeContainer<>();
// 消費者線程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
System.out.println("consumer get "+container.get());
}
}, "get ").start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 2; i++) {
new Thread(() ->{
for (int j = 0; j < 25; j++) {
container.put(j);
}
}, "put ").start();
}
}
}
使用while
假設容器中已經滿了,如果用的是if,這個線程A發現list.size()==max已經滿了,就this.wait()住了;如果容器中被拿走了元素,線程A被叫醒了,它會從this.wait()開始繼續往下運行,準備執行lists.add(),可是它被叫醒了之後還沒有往裏扔的時候,另外一個線程往list裏面扔了一個,線程A拿到鎖之後不再進行if判斷,而是繼續執行lists.add()就會出問題了;如果用while,this.wait()繼續往下執行的時候需要在while中再檢查一遍,就不會出問題。
改進
public class ResumeContainer<T> {
private List<T> lists = new ArrayList<>();
final private LinkedList<T> list = new LinkedList<>();
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
private int max_ = 10;
private int cnt = 0;
public void put(T t){
try{
lock.lock();
while (list.size() == max_){
System.out.println("=============== list full ================");
producer.await();
}
System.out.println(String.format("=============== put %s ================",String.valueOf(t)));
list.add(t);
cnt++;
consumer.signalAll();// 通知消費者線程進行消費
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public T get(){
T t = null;
try{
lock.lock();
while (list.size()==0){
consumer.await();
}
t = list.removeFirst();
System.out.println(String.format("=============== get %s ================",String.valueOf(t)));
cnt++;
producer.signalAll();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
public static void main(String[] args){
ResumeContainer<Object> container = new ResumeContainer<>();
// 消費者線程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
container.get();
}
}, "get ").start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 2; i++) {
new Thread(() ->{
for (int j = 0; j < 25; j++) {
container.put(j);
}
}, "put ").start();
}
}
}
使用wati和notify寫線程程序的時候就像使用彙編語言一樣,寫起來會比較費勁,使用lock和condition好處在於可以精確的通知那些線程被叫醒,哪些線程不必被叫醒,這個效率顯然要比notifyAll把所有線程全叫醒要高很多。
線程局部變量
public class ThreadLocal {
volatile static Person p = new Person();
public static void main(String[] args){
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.name);
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.name = "wangwu";
}).start();
}
}
class Person{
public String name = "zhangsan";
}
現在這兩個線程是互相影響的;第二個線程改了名字之後,第一個線程就能讀的到了;有的時候就想線程2的改變,不想讓線程1知道ThreadLocal線程局部變量 * * ThreadLocal是使用空間換時間,synchronized是使用時間換空間
public class ThreadLocal1 {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args){
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set(new Person());
System.out.println(tl.get());
}).start();
}
}
class Person{
public String name = "zhangsan";
}
輸出:
Basic.Person@2ee37501
null
上面代碼的輸出,在兩個線程裏面對person進行了修改,二者得輸出都是不一樣的,也就是同一個變量在倆個線程中是隔離的ThreadLocal的意思就是,tl裏面的變量,自己的線程自己用;你別的線程裏要想用的話,不好意思你自己往裏扔;不能用我線程裏面放的東西;相當於每個線程都有自己的變量,互相之間不會產生衝突;可以理解爲person對象每個線程裏面拷貝了一份,改的都是自己那份,都是自己線程本地的變量,所以空間換時間;ThreadLocal在效率上會更高一些;