北京网站建设58,怎么建商业网站,巨野有做网站的公司吗,推广比较好的网站有哪些并发之共享模型 文章目录 并发之共享模型一、多线程带来的共享问题二、解决方案三、方法中的synchronize四、变量的线程安全分析五、习题六、Monitor七、synchronize优化八、wait和notify九、sleep和wait十、park和unpark十一、重新理解线程状态十二、多把锁十三、ReentrantLoc…并发之共享模型 文章目录 并发之共享模型一、多线程带来的共享问题二、解决方案三、方法中的synchronize四、变量的线程安全分析五、习题六、Monitor七、synchronize优化八、wait和notify九、sleep和wait十、park和unpark十一、重新理解线程状态十二、多把锁十三、ReentrantLock 一、多线程带来的共享问题
比如共享资源在被两个线程使用的时候。线程1对变量i0进行1操作但是在1途中切换到了线程2线程2对变量i进行-1操作然后把-1返回到共享变量。但是再切换回线程1已经1也就是局部变量已经是1重新赋值给共享变量会导致并发问题 其二是共享资源如果一直被一个线程使用线程可能会由于sleep,wait,io等操作浪费cpu的使用时间那么可以把这段时间让给别人
问题分析 i的字节码是 getstatic i 获取静态变量i也就是主存的i。
iconst_1 准备常量1
iadd 进行相加
pustatic i
所以我们看到的i并不是一条原子指令既然不是原子指令那么线程自然可以在切换过程中导致这些指令交错执行。最后导致共享资源是个脏数据的问题。
临界区 实际上就是多个线程访问的代码里有共享资源那么这段代码就是临界区
竟态条件 如果在临界区中多线程发生执行指令序列不同导致结果无法预测的状况就是竟态条件
二、解决方案
①synchronize 线程1上锁后线程2无法获取锁不能执行临界区线程2阻塞等待线程1完成释放锁之后才能够使用。可以把synchronize类比为一个房间每次有锁的人才能够进入房间做事情就算cpu时间片 用完只要没有这个锁的钥匙其它线程是无法进入房间的。当用完之后就会释放锁并且唤醒那些阻塞的线程。
static int count0;static Object locknew Object();public static void main(String[] args) throws InterruptedException {Room room new Room();Thread t1 new Thread(() - {for (int i 0; i 5000; i) {
// room.increment();synchronized (lock){count;}}}, t1);Thread t2 new Thread(() - {for (int i 0; i 5000; i) {
// room.decrement();synchronized (lock){count--;}}}, t2);t1.start();t2.start();t1.join();t2.join();
// log.debug({}, room.getCounter());log.debug({},count);}思考 ①如果synchronize放到for外面实际上就是要执行完整个for才会把锁放出去-原子性 ②如果t1和t2使用的是不同的obj会怎么样相当于进入了不同的房间那么锁是没有效果的两个线程仍然会各自执行临界区的代码块导致执行序列不同。 ③如果t1加锁但是t2没有那就相当于t2可以随时执行临界区
面向对象改进 实际上就是把那些需要加锁的临界区和共享资源全部封装到一个类上。在方法上加synchronize相当于锁住了this。如果是静态方法那么相当于是锁住了类对象.class
class Room {private int counter 0;public synchronized void increment() {counter;}public synchronized void decrement() {counter--;}public synchronized int getCounter() {return counter;}
}三、方法中的synchronize
线程八锁 情况1没有sleep的时候n1执行a和b方法都是锁住自己的对象。所以是互斥的。 情况2:有sleep其实还是一样执行a和b方法的两个线程看谁先获取到锁。那么谁就先执行。不管里面是不是有sleep没获取到锁的线程就是要等待
Slf4j(topic c.Test8Locks)
public class Test8Locks {public static void main(String[] args) {Number n1 new Number();
// Number n2 new Number();new Thread(() - {log.debug(begin);n1.a();}).start();new Thread(() - {log.debug(begin);n1.b();}).start();}
}
Slf4j(topic c.Number)
class Number{public synchronized void a() {sleep(1);log.debug(1);}public synchronized void b() {log.debug(2);}
}情况3:这种情况c是没有加锁也就是随意都可以执行。可能出现的结果是3 12也可能是32 1也可能是23 1。
Slf4j(topic c.Test8Locks)
public class Test8Locks {public static void main(String[] args) {Number n1 new Number();
// Number n2 new Number();new Thread(() - {log.debug(begin);n1.a();}).start();new Thread(() - {log.debug(begin);n1.b();}).start();new Thread(() - {log.debug(begin);n1.c();}).start();}
}
Slf4j(topic c.Number)
class Number{public synchronized void a() {sleep(1);log.debug(1);}public synchronized void b() {log.debug(2);}public void c(){log.debug(3);}
}情况4这种他们绑定的锁都是不同的相当于就是无锁。最后的结果肯定是21。因为1线程sleep了
Slf4j(topic c.Test8Locks)
public class Test8Locks {public static void main(String[] args) {Number n1 new Number();Number n2 new Number();new Thread(() - {log.debug(begin);n1.a();}).start();new Thread(() - {log.debug(begin);n2.b();}).start();// new Thread(() - {
// log.debug(begin);
// n1.c();
// }).start();}
}
Slf4j(topic c.Number)
class Number{public synchronized void a() {sleep(1);log.debug(1);}public synchronized void b() {log.debug(2);}
//
// public void c(){
// log.debug(3);
// }
}情况5-8都是加上了static那么就根据他们锁的对象来判断是不是同一把锁就可以了
四、变量的线程安全分析
静态变量和成员变量是否有线程安全问题 如果只是读就没有如果是读写就要关注临界区
局部变量 如果是引用类型的话那么就有。 局部变量的值存储在线程的栈帧里面也就是私有的。而不是像static变量那样先从方法区中取出这个变量然后再进行对应的修改。 情况1ThreadUnsafe的list是在类里面创建的那么就会造成线程处理的是堆里面同一个list导致的线程安全问题
情况2把list放到method1里面那么就是一个局部变量的引用而且每个线程调用方法后都有自己的一个list。那么就不会造成线程安全问题
情况3有子类重写了这个方法那么也能获取这个list导致多个线程能够操作list。解决办法就是给方法加上final防止子类重写。
public class TestThreadSafe {static final int THREAD_NUMBER 2;static final int LOOP_NUMBER 200;public static void main(String[] args) {
// ThreadSafeSubClass test new ThreadSafeSubClass();ThreadUnsafe testnew ThreadUnsafe();for (int i 0; i THREAD_NUMBER; i) {new Thread(() - {test.method1(LOOP_NUMBER);}, Thread (i1)).start();}}
}
class ThreadUnsafe {ArrayListString list new ArrayList();public void method1(int loopNumber) {for (int i 0; i loopNumber; i) {method2();method3();}}private void method2() {list.add(1);}private void method3() {list.remove(0);}
}class ThreadSafe {public final void method1(int loopNumber) {ArrayListString list new ArrayList();for (int i 0; i loopNumber; i) {method2(list);method3(list);}}public void method2(ArrayListString list) {list.add(1);}private void method3(ArrayListString list) {System.out.println(1);list.remove(0);}
}class ThreadSafeSubClass extends ThreadSafe{
// Overridepublic void method3(ArrayListString list) {System.out.println(2);new Thread(() - {list.remove(0);}).start();}
}线程安全类 Integer HashTable String Random Vector JUC下的类
它们的单个方法是线程安全的但是多个方法执行的时候就不一样了。 下面的代码出现的问题就是线程1判断成功之后切换刚好释放了锁然后就是线程2获取锁进行判断再次切换线程1获取锁处理put切换线程2也可以获取锁处理put。因为单个方法执行完就会释放锁。所以这样还是需要整体上加锁才能够继续处理。 不可变类线程安全 String String和Integer都是不可变的String本质上就是一个char[]数组。如果是substring方法实际上就是复制一个新的数组出来然后再给String的char数组进行赋值。replace实际上也是创建数组然后对比愿数组的旧值如果是旧值那么直接给新的数组那个位置赋新值。
public String replace(char oldChar, char newChar) {if (oldChar ! newChar) {int len value.length;int i -1;char[] val value; /* avoid getfield opcode */while (i len) {if (val[i] oldChar) {break;}}if (i len) {//创建新数组char buf[] new char[len];for (int j 0; j i; j) {buf[j] val[j];}while (i len) {char c val[i];//根据原来的数组是旧值的位置改变成新值buf[i] (c oldChar) ? newChar : c;i;}return new String(buf, true);}}return this;}public String substring(int beginIndex) {if (beginIndex 0) {throw new StringIndexOutOfBoundsException(beginIndex);}int subLen value.length - beginIndex;if (subLen 0) {throw new StringIndexOutOfBoundsException(subLen);}//实际上就是创建了一个新的String而不是修改了值return (beginIndex 0) ? this : new String(value, beginIndex, subLen);
}public String(char value[], int offset, int count) {if (offset 0) {throw new StringIndexOutOfBoundsException(offset);}if (count 0) {if (count 0) {throw new StringIndexOutOfBoundsException(count);}if (offset value.length) {this.value .value;return;}}// Note: offset or count might be near -11.if (offset value.length - count) {throw new StringIndexOutOfBoundsException(offset count);}//实际上就是创建新数组并且进行复制this.value Arrays.copyOfRange(value, offset, offsetcount);}实例分析 这种线程不安全原因就是这个MyServlet是共享的而且UserService是在堆里面的。可以被多个线程调用它的方法修改count可能会引发并发问题。 这种也是有并发问题的单例共享可以被多个多线程调用覆盖start这种变量。 这种不会发生线程安全问题原因是没有任何变量可以被修改。 这里是会发生线程安全问题原因是Connection暴露出去可以被多个线程处理。如果线程1在处理getCon的时候切换到线程2刚好close那么线程1的Connection就没办法继续执行因为已经被修改了。 这个地方没有线程安全问题原因是每次都是新创建的一个UserDao相当于是一个局部变量。并不是一个共享资源 这个地方可能会出现线程安全问题是因为通过子类方法把局部变量暴露出去可能会被子类对象通过线程把这个局部变量进行修改 总结线程是否安全取决于是否有能够被修改的共享资源
五、习题
出现线程安全问题的是在卖票的时候可能多个线程在卖票同时取出了共享变量count最后的count数量就是最后那个线程处理的值。而不是共同处理的值因为他们的执行序列发生了错误导致count没有等待处理完就被另外一个线程先读取进来了。
解决办法就是给临界区加锁实际上就是window的sell。那么为什么不给amountList和window加锁原因是他们操作的并不是同一个共享资源处理不同自然就不需要加锁。但是之前的HashTable两个操作get和put都是针对同一个共享资源导致最后的value会被最后的那个线程覆盖。因为判空成功的时候线程1还没有完成put。而且也没有锁住两个操作而是做一个放一个
Slf4j(topic c.ExerciseSell)
public class ExerciseSell {public static void main(String[] args) throws InterruptedException {// 模拟多人买票TicketWindow window new TicketWindow(1000);// 所有线程的集合ListThread threadList new ArrayList();// 卖出的票数统计ListInteger amountList new Vector();for (int i 0; i 2000; i) {Thread thread new Thread(() - {// 买票try {Thread.sleep(random(10));} catch (InterruptedException e) {e.printStackTrace();}int amount window.sell(random(5));// 统计买票数amountList.add(amount);});threadList.add(thread);thread.start();}for (Thread thread : threadList) {thread.join();}// 统计卖出的票数和剩余票数log.debug(余票{},window.getCount());log.debug(卖出的票数{}, amountList.stream().mapToInt(i- i).sum());}// Random 为线程安全static Random random new Random();// 随机 1~5public static int random(int amount) {return random.nextInt(amount) 1;}
}// 售票窗口
class TicketWindow {private int count;public TicketWindow(int count) {this.count count;}// 获取余票数量public int getCount() {return count;}// 售票 synchronizedpublic int sell(int amount) {if (this.count amount) {this.count - amount;return amount;} else {return 0;}}
}Vector自己的方法就已经带锁public synchronized boolean add(E e) {modCount;ensureCapacityHelper(elementCount 1);elementData[elementCount] e;return true;}转账问题 这里其实会发生线程安全问题。主要就是在a转账的同时b也进去转账那么b获取到的肯定就是没有转账的a相对a也是。那么直接给方法加上synchronize行不行如果绑定的是本类对象很明显是不行因为是两个账户锁是不同的进入了不同房间。那么解决方案就是可以使用唯一的Account.class的本类那么就能够锁住了
Slf4j(topic c.ExerciseTransfer)
public class ExerciseTransfer {public static void main(String[] args) throws InterruptedException {Account a new Account(1000);Account b new Account(1000);Thread t1 new Thread(() - {for (int i 0; i 1000; i) {a.transfer(b, randomAmount());}}, t1);Thread t2 new Thread(() - {for (int i 0; i 1000; i) {b.transfer(a, randomAmount());}}, t2);t1.start();t2.start();t1.join();t2.join();// 查看转账2000次后的总金额log.debug(total:{}, (a.getMoney() b.getMoney()));}// Random 为线程安全static Random random new Random();// 随机 1~100public static int randomAmount() {return random.nextInt(100) 1;}
}// 账户
class Account {private int money;public Account(int money) {this.money money;}public int getMoney() {return money;}public void setMoney(int money) {this.money money;}// 转账public void transfer(Account target, int amount) {synchronized(Account.class) {if (this.money amount) {this.setMoney(this.getMoney() - amount);target.setMoney(target.getMoney() amount);}}}
}六、Monitor
java对象头 包括了markword主要就是存储hashcode,age(gc生命值),biase_lock是不是偏向锁01加锁的情况 还有就是klassword主要就是指向类对象类的信息。 如果是数组那么还包含了数组的长度 Monitor锁 monitor锁是os提供的成本很高 工作原理 实际上就是把obj的markword前面30bit记录monitor的地址指向monitor。然后如果有线程要执行临界区代码时就把monitor的owner指向对应的线程。如果又有线程进来那么看看obj是否关联锁然后再看看是否有owner如果有那么就进入到EntryList阻塞等待。等待线程释放锁之后唤醒EntryList然后重新开始竞争。 字节码的角度 字节码的角度其实就是先把lock的引用复制放到slot1然后就是monitorentry把lock的markword指向monitor并且把hashcode等存入monitor。接着执行业务代码最后就是取出引用slot1然后就是monitorexit解锁。而且对业务代码也就是同步块进行了异常监视如果出现异常那么还是会进行解锁操作的
七、synchronize优化
小故事 如果两个线程之间没有竞争那么就可以使用轻量级锁相当于就是挂书包如果发现是对方书包那么就在外面等待。后来另一个线程不用了那么另一个线程就可以刻名字在门外相当于就是偏向锁如果这个时候有人来竞争那么就会升级为书包也就是轻量级锁。后来另一线程回来了发现那个线程把很多个门都刻上名字就去找os把那些名字批量刻成自己的。也就是修改偏向锁。最后名字实在刻太多取消了偏向。
偏向锁是单个线程专属的如果单个线程处理某个代码没有竞争那么就可以使用偏向锁如果有竞争那么就可以升级为轻量级锁。 1.轻量级锁 本质就是线程的调用临时区方法的栈帧的锁记录保存对象的引用和对象markword的信息。接着就是把对应锁记录的锁信息与obj进行交换比如说把01改成了00告诉obj这是一个轻量级锁而且告诉了obj锁记录的地址相当于就是给obj贴上是谁的锁的标签。如果是可重入锁那么锁记录markword部分就是null表示的是这是可重入的用的是同一个锁。 2.锁膨胀 其实就是竞争轻量级锁的时候没有地方给竞争的线程放着那么这个时候就需要把轻量级锁转换成重量级锁monitor其实就是把obj的markword指向monitor。然后就是monitor的owner指向当前线程的锁记录。把阻塞线程放到等待队列里面。 恢复的时候CAS尝试把线程的锁记录给恢复过去但是发现失败。这个时候恢复方式改成了重量级锁的恢复方式唤醒list然后owner设置为null线程重新竞争monitor。如果没有就把monitor保存的hashcode信息恢复。 3.自旋优化 实际上就相当于等红绿灯如果很快到绿灯就等一会如果还有很久那么就拉手刹。自旋就是旋多一会等别人释放重量级锁如果成功一次那么下次就会确定成功几率加大自旋多几次。如果没有等到那么就阻塞。 自旋的原因阻塞会导致线程的上下文切换需要消耗cpu时间和资源。速度相对比较慢。
4.偏向锁 之所以要使用偏向锁是因为轻量级锁的锁重入每次都调用CAS进行对比CAS是一个OS指令操作所以速度很慢。所以偏向锁是把ThreadId直接赋值给markword那么下次就能直接在java上对比这个markword。 偏向锁带有延迟性通常对象创建过一会才会生成 先生成偏向锁-》轻量级锁-》重量级锁 如果给临界区使用偏向锁那么对应执行线程的id赋值给markword 如果使用了锁的hashcode那么偏向锁就会被禁止因为hashcode占用的bit太多 轻量级在锁记录上记录hashcode重量级在monitor上记录 如果两个线程用同一个偏向级锁那么锁会变成不可偏向的升级为轻量级锁 批量重偏向 其实就是多个没有竞争的线程使用同一个锁如果jvm发现撤销的锁偏向次数超过20次那么就会自动偏向另外一个线程。比如t1线程使用一堆锁锁偏向t1。但如果t2使用这些锁并且需要撤销锁但偏向超过20次那么这些锁会全部偏向t2
批量撤销 如果撤销超过40次那么jvm就会撤销所有对象的偏向
5.锁消除 其实就是JIT发现锁的临界区里根本没有共享资源那么就取消了这个锁
八、wait和notify
小故事 小南需要烟才能工作但他又要占着锁让别人无法进来。那么这个时候开一个waitSet相当于就是休息室让小南进去休闲并且释放锁。如果烟到了那么notify小南就能够继续工作了。
Blocked和Waiting区别 其实就是waiting释放了锁blocked是没有锁 waiting被notify之后仍然需要进入到entrylist进行等待 wait和notify的规则 线程调用对象wait和notify的时候一定是要使用这个锁成为monitor的主人的时候。这样才能够wait释放锁和被其它获取这个锁的人notify
Slf4j(topic c.TestWaitNotify)
public class TestWaitNotify {final static Object obj new Object();public static void main(String[] args) {new Thread(() - {synchronized (obj) {log.debug(执行....);try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug(其它代码....);}},t1).start();new Thread(() - {synchronized (obj) {log.debug(执行....);try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug(其它代码....);}},t2).start();// 主线程两秒后执行sleep(0.5);log.debug(唤醒 obj 上其它线程);synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程obj.notifyAll(); // 唤醒obj上所有等待线程}}
}wait()方法可以限制等待的时间wait(参数)
九、sleep和wait
区别 sleep:Thread调用静态方法而且不会释放锁 wait:所有obj但是要配合synchronized使用可以释放锁 扩展 通常锁会加上final防止被修改
正确使用 小南需要烟才能工作如果是使用sleep不释放锁那么其它需要等待干活的人就会干等着。但是wait可以让小南释放锁让其他线程工作并且唤醒小南 存在问题 会不会有其他线程在等待着锁如果是那么会不会唤醒错了线程
Slf4j(topic c.TestCorrectPosture)
public class TestCorrectPostureStep2 {static final Object room new Object();static boolean hasCigarette false;static boolean hasTakeout false;public static void main(String[] args) {new Thread(() - {synchronized (room) {log.debug(有烟没[{}], hasCigarette);if (!hasCigarette) {log.debug(没烟先歇会);try {room.wait(2000);} catch (InterruptedException e) {e.printStackTrace();}}log.debug(有烟没[{}], hasCigarette);if (hasCigarette) {log.debug(可以开始干活了);}}}, 小南).start();for (int i 0; i 5; i) {new Thread(() - {synchronized (room) {log.debug(可以开始干活了);}}, 其它人).start();}sleep(1);new Thread(() - {synchronized (room) {hasCigarette true;log.debug(烟到了噢);room.notify();}}, 送烟的).start();}}解决办法 可以通过while多次判断条件是否成立直接使用notifyAll来唤醒所有的线程。然后线程被唤醒之后可以先再次判断条件是否成立成立那么往下面执行如果不成立那么继续wait。
十、park和unpark
与wait和notify的区别 不需要与monitor一起使用 可以精准唤醒和阻塞线程 可以先unpark但是不能先notify。但是unpark之后park就不起作用了
工作原理 ①park先去到counter里面判断是不是0如果是那么就让线程进入队列并且再次把counter设置为0 ②unpark,如果线程正在阻塞那么先把counter置为1然后唤醒线程恢复运行并把counter设置为0 ③先unpark后park,那么就unpark补充counter为1park判断counter是1认为还有体力就继续执行
十一、重新理解线程状态
情况1:new-runnable 线程start 情况2:runnable-waiting notify和wait。wait进入阻塞notify让他们重新竞争锁进入runnable其他还是进入blocked 情况3 park和unpark 情况4 t.join调用它的线程会进入等待等待t完成任务或者是被interrupt 情况5-8 其实就是waitjoinsleep加上时间而已都是从runnable-blocked 情况9 synchronize获取锁失败那么就会进入blocked 情况10 所有代码执行完那么就是terminated
十二、多把锁
一个房间睡觉和学习。但是只有一把锁睡觉的时候不能学习并发度非常低。那么这个时候可以通过细化锁的粒度分成两把锁一把是学习房间的锁一把是卧室的锁那么就能够让两个功能并发执行。
问题 如果锁太多一个线程需要多把锁会导致死锁的发生
public class TestMultiLock {public static void main(String[] args) {BigRoom bigRoom new BigRoom();new Thread(() - {bigRoom.study();},小南).start();new Thread(() - {bigRoom.sleep();},小女).start();}
}Slf4j(topic c.BigRoom)
class BigRoom {private final Object studyRoom new Object();private final Object bedRoom new Object();public void sleep() {synchronized (this) {log.debug(sleeping 2 小时);Sleeper.sleep(2);}}public void study() {synchronized (this) {log.debug(study 1 小时);Sleeper.sleep(1);}}}死锁的案例 t1有A但是想要Bt2有B但是想要A
Slf4j(topic c.TestDeadLock)
public class TestDeadLock {public static void main(String[] args) {test1();}private static void test1() {Object A new Object();Object B new Object();Thread t1 new Thread(() - {synchronized (A) {log.debug(lock A);sleep(1);synchronized (B) {log.debug(lock B);log.debug(操作...);}}}, t1);Thread t2 new Thread(() - {synchronized (B) {log.debug(lock B);sleep(0.5);synchronized (A) {log.debug(lock A);log.debug(操作...);}}}, t2);t1.start();t2.start();}
}死锁的检查方式 1、jps定位进程idjstack id来查看程序信息 2、jconsole直接查看进程信息
活锁 其实就是两个线程都在改变对方的解锁条件导致没有释放锁但没有阻塞。死锁就是含有对方的锁不放开导致线程阻塞。 解决方案 可以通过改变线程执行的时间让他们交错执行快速执行解锁条件。 饥饿问题 其实就是线程一直获取竞争不到锁导致没有执行。
十三、ReentrantLock
相比synchronize 可以被中断 可以设置获取超时超时之后就自动放弃获取锁 公平锁防止饥饿问题 条件变量多
可重入 只要是同一个线程获取同一把锁那么就能够被使用第二次。在没有被解锁的时候可被使用第二次
Slf4j(topic c.test22)
public class MyTest22 {public static ReentrantLock locknew ReentrantLock();public static void main(String[] args) {lock.lock();try{log.debug(开始进入m1);m1();}finally {lock.unlock();}}public static void m1(){lock.lock();try {log.debug(m1进入);m2();}finally {lock.unlock();}}public static void m2(){lock.lock();try{log.debug(m2进入);}finally {lock.unlock();}}
}可中断 lockInterrupt,这个方法才能够被其它线程中断等待锁。如果是lock那么就算中断也没有任何效果。这种可中断可以减少死锁的发生。
Slf4j(topic c.test23)
public class MyTest23 {public static ReentrantLock locknew ReentrantLock();public static void main(String[] args) {Thread t1 new Thread(() - {log.debug(上锁);lock.lock();
// try {
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// e.printStackTrace();
// log.debug(无法获取锁);
// }try {log.debug(获取到锁);} finally {lock.unlock();}}, t1);t1.start();lock.lock();Sleeper.sleep(1);log.debug(帮助t1解锁);t1.interrupt();}
}超时 这个地方可以使用tryLock来设定获取锁的超时时间如果超时那么就自动放弃获取锁。而不是一直锁住
Slf4j(topic c.test24)
public class MyTest24 {public static ReentrantLock locknew ReentrantLock();public static void main(String[] args) {Thread t1 new Thread(() - {log.debug(尝试获得锁);try {if(!lock.tryLock(2, TimeUnit.SECONDS)){log.debug(获取锁失败);return ;}} catch (InterruptedException e) {e.printStackTrace();log.debug(获取锁失败1);return;}try{log.debug(获取锁成功);}finally {lock.unlock();}}, t1);log.debug(main获取锁);lock.lock();t1.start();Sleeper.sleep(1);log.debug(main解锁);lock.unlock();}
}解决哲学家的问题 思路 可以使用ReentrantLock的tryLock如果尝试失败你那么就会往下面执行而不是一直等待获得锁获取不到是不会阻塞线程的。 Overridepublic void run() {while (true) {if(left.tryLock()){try{if(right.tryLock()){try{eat();}finally {right.unlock();}}}finally {left.unlock();}}}}条件变量 定义 synchronize可以有一把锁并且通过wait和notify来释放锁进入到waitSet。对于ReentrantLock就相当于有多个休息室waitSet创建锁之后可以创建多个条件变量多个房间可以认为ReentrantLock里面有多个休息室进入不同的休息室可以通过不同的小锁处理。但实际上释放的锁还是ReentrantLock然后交给别人使用只不过通过条件变量可以控制住不同的房间让同房间但线程区竞争锁。
这里就是使用了条件变量但他们进入但线程房间不同操作的方式是相同的。唤醒的房间的线程不相同。
Slf4j(topic c.Test24)
public class Test224 {static final Object room new Object();static boolean hasCigarette false;static boolean hasTakeout false;static ReentrantLock ROOM new ReentrantLock();// 等待烟的休息室static Condition waitCigaretteSet ROOM.newCondition();// 等外卖的休息室static Condition waitTakeoutSet ROOM.newCondition();public static void main(String[] args) {new Thread(() - {ROOM.lock();try {log.debug(有烟没[{}], hasCigarette);while (!hasCigarette) {log.debug(没烟先歇会);try {waitCigaretteSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(可以开始干活了);} finally {ROOM.unlock();}}, 小南).start();new Thread(() - {ROOM.lock();try {log.debug(外卖送到没[{}], hasTakeout);while (!hasTakeout) {log.debug(没外卖先歇会);try {waitTakeoutSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(可以开始干活了);} finally {ROOM.unlock();}}, 小女).start();sleep(1);new Thread(() - {ROOM.lock();try {hasTakeout true;waitTakeoutSet.signal();} finally {ROOM.unlock();}}, 送外卖的).start();sleep(1);new Thread(() - {ROOM.lock();try {hasCigarette true;waitCigaretteSet.signal();} finally {ROOM.unlock();}}, 送烟的).start();}}用锁来固定顺序执行 思路 其实就是两个线程都需要用到这个锁但是t1线程一定要t2线程运行之后才能运行那么判断条件就是一个boolean如果t2运行那么就修改并且唤醒线程t1。线程t1如果发现t2没有运行那么wait进入等待如果被虚假唤醒可以通过while来循环进入重新等待。
Slf4j(topic c.25)
public class MyTest25 {static Object locknew Object();static boolean t2Runfalse;public static void main(String[] args) {Thread t1 new Thread(() - {synchronized (lock){try {while(!t2Run){lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}log.debug(1);}}, t1);Thread t2 new Thread(() - {synchronized (lock){log.debug(2);//唤醒线程1t2Runtrue;lock.notify();}}, t2);t1.start();t2.start();}
}
第二种方法 其实就是使用LockSupport的park方法处理。这种t1如果先执行那么就会park进入阻塞然后t2执行之后unpark唤醒t1。如果是t2先执行也没关系这里线程的unpark会把counter变成1t1如果park先检查counter发现是1那么就可以继续执行。
Slf4j(topic c.26)
public class MyTest26 {public static void main(String[] args) {Thread t1 new Thread(() - {LockSupport.park();log.debug(1);}, t1);Thread t2 new Thread(() - {log.debug(2);LockSupport.unpark(t1);}, t2);t1.start();t2.start();}
}轮流打印abc的思路交替执行 synchronized方式 其实还是对是否到这个线程的flag进行判断如果是1那么就t1执行如果是2那么就t2执行。执行之后还需要唤醒其它线程来查看是不是自己的条件如果不是那么就继续进入等待条件符合条件就获取锁继续执行。
public class MyTest27 {public static void main(String[] args) {WaitNotify1 waitNotify1 new WaitNotify1(1, 5);Thread t1 new Thread(() - {waitNotify1.print(a,1,2);}, t1);Thread t2 new Thread(() - {waitNotify1.print(b,2,3);}, t2);Thread t3 new Thread(() - {waitNotify1.print(c,3,1);}, t3);t1.start();t2.start();t3.start();}
}class WaitNotify1{public void print(String s,int waitFlag,int nextFlag){synchronized (this){try {for(int i0;iloopNum;i){//如果不是1那么就等待while(this.flag!waitFlag){this.wait();}System.out.print(s);this.flagnextFlag;this.notifyAll();}} catch (InterruptedException e) {e.printStackTrace();}}}private int flag;private int loopNum;public WaitNotify1(int flag,int loopNum) {this.flag flag;this.loopNum loopNum;}
}ReentrantLock处理顺序执行问题思路 这个地方执行的思路其实就是通过lock开多几个条件变量条件变量控制的是各个休息室使用条件变量来吧线程阻塞之后再释放。a,b,c分别开了3个然后就是执行一个之后调用另一个condition来解锁另外一个继续执行。一开始需要把a,b,c三个线程都上锁接着就是手动解锁一个让循环开始执行。
public class MyTest28 {public static void main(String[] args) {WaitLock waitLock new WaitLock(5);Condition a waitLock.newCondition();Condition b waitLock.newCondition();Condition c waitLock.newCondition();new Thread(()-{waitLock.print(a,a,b);},t1).start();new Thread(()-{waitLock.print(b,b,c);},t2).start();new Thread(()-{waitLock.print(c,c,a);},t3).start();Sleeper.sleep(1);waitLock.lock();try{//唤醒aa.signal();}finally {waitLock.unlock();}}
}Slf4j(topic c.lock)
class WaitLock extends ReentrantLock{private int loopNum;public WaitLock(int loopNum) {this.loopNum loopNum;}public void print(String str,Condition cur,Condition next){for(int i0;iloopNum;i){lock();try{cur.await();System.out.print(str);next.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {unlock();}}}
}LockSupport执行思路 其实这个地方直接就可以使用park先让三个线程阻塞然后再手动开启一个线程。如果需要让阻塞的线程开启就需要unpark(t)就是需要传入对应的线程参数唤醒对应的线程。t1执行完之后唤醒t2t2执行完之后唤醒t3那么这个时候调用的方法就要传入对应的线程参数。而且线程参数是可以共享的放到方法区如果是放到main线程上是无法看到的。
Slf4j(topic c.test29)
public class MyTest29 {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) {WaitPark waitPark new WaitPark(5);t1new Thread(()-{waitPark.print(a,t2);},t1);t2new Thread(()-{waitPark.print(b,t3);},t2);t3new Thread(()-{waitPark.print(c,t1);},t3);t1.start();t2.start();t3.start();Sleeper.sleep(1);LockSupport.unpark(t1);}
}
Slf4j(topic c.lock)
class WaitPark{private int loopNum;public WaitPark(int loopNum) {this.loopNum loopNum;}public void print(String str,Thread t){for(int i0;iloopNum;i){LockSupport.park();log.debug(str);LockSupport.unpark(t);}}
}