1.闭锁CountDownLatch
闭锁:想实现它管理的线程都执行完后,在执行其它线程。
在调用构造方法创建CountDownLatch对象时需要指定管理线程的个数(计数器的值)。
await():会产生阻塞,直到计数器减为0的时候才会释放。
countDown():每调用一次,会将计数器–。
例子:锅和菜买回来之后,才可以输出开始做饭
//两个线程类
class BuyGuo implements Runnable{
private CountDownLatch cdl ;
public BuyGuo(CountDownLatch cdl){
this.cdl = cdl;
}
public void run(){
System.out.println("锅买回来了...");
cdl.countDown();
}
}
class BuyCai implements Runnable{
private CountDownLatch cdl;
public BuyCai(CountDownLatch cdl){
this.cdl = cdl;
}
public void run(){
System.out.println("菜买回来了...");
cdl.countDown();
}
}
//主方法
public static void main(String[] args) {
//创建闭锁,管理2个线程
CountDownLatch cdl = new CountDownLatch(2);
//将闭锁传入。
new Thread(new BuyGuo(cdl)).start();
new Thread(new BuyCai(cdl)).start();
try {
//阻塞,直到上述2个线程执行完毕再继续执行。
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始做饭...");
}
2.栅栏 CyclicBarrier
也需要在创建时确定管理线程个数,await()方法是加法了,产生阻塞并且计数器加一,当加到管理线程的个数时,释放阻塞。
public class DemoCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(2);
new Thread(new Horse1(cb)).start();
new Thread(new Horse2(cb)).start();
}
}
class Horse1 implements Runnable{
private CyclicBarrier cb;
public Horse1(CyclicBarrier cb){
this.cb = cb;
}
public void run(){
System.out.println("第一匹马来到起跑线,做好了准备..");
try {
//阻塞,知道计数器为2时才释放。
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("第一匹马开始比赛...跑起");
}
}
class Horse2 implements Runnable{
private CyclicBarrier cb;
public Horse2(CyclicBarrier cb){
this.cb = cb;
}
public void run(){
System.out.println("第二匹马正在拉肚子ing....");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二匹马来到了起跑线,准备好...");
try {
//阻塞,知道计数器为2时才释放。
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("第二匹马开始比赛...跑起");
}
}
//两匹马同时起跑~~
CountDownLatch和CyclicBarrier的主要联系和区别如下:(这段转自千山独行大大的博客~)
1.闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。
2.CountDownLatch是一次性的,CyclicBarrier可以重用。
3.CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成。
4.鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能。
3.交换机 Exchanger
public class DemoExchanger {
public static void main(String[] args) {
//创建交换机。
Exchanger<String> exc = new Exchanger<String>();
new Thread(new Spy1(exc)).start();
new Thread(new Spy2(exc)).start();
}
}
class Spy1 implements Runnable{
private Exchanger<String> exc;
public Spy1(Exchanger<String> exc){
this.exc = exc;
}
public void run(){
//返回的是线程2传给线程1的内容
try {
//该方法给线程传信息,并接收对方传的信息。
String msg = exc.exchange("天王盖地虎");
System.out.println("间谍2传给间谍1的信息:"+msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Spy2 implements Runnable{
private Exchanger<String> exc;
public Spy2(Exchanger<String> exc){
this.exc = exc;
}
public void run(){
//返回的是线程2传给线程1的内容
try {
//该方法给线程传信息,并接收对方传的信息。
String msg = exc.exchange("宝塔镇河妖");
System.out.println("间谍1传给间谍2的信息:"+msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.锁—lock
之前线程安全问题需要解决,都会用synchronized同步代码块,这个方法我们还需要指定锁的钥匙,不够灵活。
synchronized:
假设切换线程时,线程1运行一次,然后等待,唤醒线程2,线程2运行。这期前假设运行时间1s,唤醒时间2秒。那么运行2次的时间一共是4秒。
lock:lock在唤醒期间,如果线程2没醒会多次运行,线程1运行1秒后,线程2处再唤醒中,那么线程1会在唤醒的2秒中继续运行2次,等线程2唤醒后,线程2运行。那么4秒的时间一共运行了4次。
lock代码具体如下:
public class TestDemo3 {
public static String name="李雷";
public static String gender = "男";
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(new ReadRunner3(lock)).start();
new Thread(new WriteRunner3(lock)).start();
}
}
class ReadRunner3 implements Runnable{
private Lock lock;
public ReadRunner3(Lock lock){
this.lock = lock;
}
public void run(){
while(true){
//添加锁
lock.lock();
System.out.println(TestDemo3.name+","+TestDemo3.gender);
//释放锁,如果涉及到异常处理的代码,该行代码一定要放在finally中
lock.unlock();
}
}
}
class WriteRunner3 implements Runnable{
private Lock lock;
public WriteRunner3(Lock lock){
this.lock= lock;
}
public void run(){
while(true){
lock.lock();
if("李雷".equals(TestDemo3.name)){
TestDemo3.name = "韩梅梅";
TestDemo3.gender ="女";
}else{
TestDemo3.name="李雷";
TestDemo3.gender="男";
}
lock.unlock();
}
}
}
读写锁ReadWriteLock
读写锁分为读锁和写锁,与Lock的区别就是,读锁和读锁之间可以共存,如果是对一条数据的多个请求的读操作,不会进行锁定,但是读锁和写锁,写锁和写锁会被锁定。
代码升级:
public class TestDemo4 {
public static String name="李雷";
public static String gender = "男";
public static void main(String[] args) {
ReadWriteLock lock = new ReentrantReadWriteLock ();
new Thread(new ReadRunner4(lock)).start();
new Thread(new WriteRunner4(lock)).start();
}
}
class ReadRunner4 implements Runnable{
private ReadWriteLock lock;
public ReadRunner4(ReadWriteLock lock){
this.lock = lock;
}
public void run(){
while(true){
//添加锁
lock.readLock().lock();
System.out.println(TestDemo4.name+","+TestDemo4.gender);
//释放锁,如果涉及到异常处理的代码,该行代码一定要放在finally中
lock.readLock().unlock();
}
}
}
class WriteRunner4 implements Runnable{
private ReadWriteLock lock;
public WriteRunner4(ReadWriteLock lock){
this.lock= lock;
}
public void run(){
while(true){
lock.writeLock().lock();
if("李雷".equals(TestDemo4.name)){
TestDemo4.name = "韩梅梅";
TestDemo4.gender ="女";
}else{
TestDemo4.name="李雷";
TestDemo4.gender="男";
}
lock.writeLock().unlock();
}
}
}
5.原子性AtomicInteger
如果设置一个静态成员变量,2个线程分别从1加到100000,那么结束后输出最后的结果,会是多少呢?
会是200000嘛?不会,因为线程安全没有任何措施,导致会有重复的相加,即两个线程同时加完后结果只加了1.而不是加2.
添加synchronized同步代码块或锁可以解决该问题,我们还有别的解决方案,原子性AtomicInteger
public class DemoAtomic {
//新建原子性变量。
public static AtomicInteger num =
new AtomicInteger(0);
public static void main(String[] args) {
//闭锁,全部完成后再输出结果
CountDownLatch cdl = new CountDownLatch(2);
new Thread(new AddRunner(cdl)).start();
new Thread(new AddRunner(cdl)).start();
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num);
}
}
class AddRunner implements Runnable{
private CountDownLatch cdl;
public AddRunner(CountDownLatch cdl){
this.cdl = cdl;
}
public void run(){
for (int i = 0; i < 100000; i++) {
//每次加一 相当于num++
DemoAtomic.num.getAndAdd(1);
}
cdl.countDown();
}
}
其源码的逻辑是每次加完后会进行安全检查,如果发现重复相加则再加一次,直至没有重复相加。