【多线程与高并发3】常用锁实例

各式锁的实际应用





乐观锁 cas(要改的对象,期望的值,要给的值)无锁操作,其实是一个乐观锁…cas本身可以看成是一个锁


  • automic : 一种使用 cas 实现的原子性操作




原子操作的简单方法:


函数 效果 备注
AtomicInteger a = new AtomicInteger(0); int a = 0; 创建对象a并且赋初值为0;
a.incrementAndGet( ); i++; 对原值+1后返回;
a.getAndIncrement( ); ++i; 对原值返回后+1;
a.addAndGet(i); a+=i; 返回a+i;
a.getAndAdd(i); a+=i; 返回原值之后给a+i;

在线程很多的情况下:LongAdder(分段锁:在线程多的时候有优势) > Atomic > synchronized。



Synchronized 的可重入性:


//可重入:
synchronized void m1(){
for(int i = 1;i<10;i++){
 try{
 	TimeUtil.SECONDS.sleep(1);// 睡一秒
	}catch(InterruptedException e){
 	e.printStackTrace();
	}
 sout(i);
}

}

synchronized void m2(){sout("m2...");}

public static void main(String[] args){
T01_ReentrantLock1 r1 = new T01_ReentrantLock1();
new Thread(r1::m1).start();
try{
 TimeUtil.SECONDS.sleep(1);// 睡一秒
}catch(InterruptedException e){
 e.printStackTrace();
}
new Thread(r1::m2).start();
}

输出结果:0 1 23 4 5 6 7 8 9 m2…

代码修改:synchronized

//可重入:
synchronized void m1(){
  for(int i = 1;i<10;i++){
    try{
    	TimeUtil.SECONDS.sleep(1);// 睡一秒
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    sout(i);
    if(i == 2){
    	new Thread(r1::m2).start();
    }
  }
}

synchronized void m2(){sout("m2...");}

public static void main(String[] args){
  T01_ReentrantLock1 r1 = new T01_ReentrantLock1();
  new Thread(r1::m1).start();
  try{
    TimeUtil.SECONDS.sleep(1);// 睡一秒
  }catch(InterruptedException e){
    e.printStackTrace();
  }

}

输出结果:0 1 2 m2 … 3 4 5 6 7 8 9




lock():替代 synchronized 的方法;


Lock lock = new ReentrantLock();
  • 特点:

    • 需要手动上锁 lock.lock( );
    • 需要手动解锁lock.unlock( );
    • 防止进程出错而导致死锁,需要try{ …… }catch( ){ …… }

  • 优点:

    • 可以使用tryLock()尝试上锁;

    • synchronized遇到锁之后只能等待,而tryLock()可以自定义等待时间;

    • locked = lock.tryLock(SECONDS(时间长度),TimeUtil.SECONDS(时间格式:秒));
      

  • 常用方法:

方法 参数 用法
.lock( ); null 锁定
.unlock( ); null 释放
.tryLock(n,TimeUtil.SECONDS); 时间长度
时间单位
等待参数时间过程中:
如果当前进程释放了,则锁定;
不释放则不锁定;
.lockInterruptibly( ); null; 可以相应被打断的锁;
.interrupt( ); Null; 打断这个锁;




公平锁

ReetrantLock lock = new ReentrantLock( true );


  • 概念:
    • 当执行队列中有线程正在排队的时候:
      • 公平锁:继续等待,排队执行;
      • 不公平锁:不等待,直接抢,有可能抢到第一个执行;
  • 创建方式:
    • 在创建锁的时候加个 true 创建出来的就是公平锁;
public class T05_ReentrantLock extends Thread(){
	private stratic ReentrantLock lock = new ReentrantLock(true);
  public void run(){
    for(int i = 0;i<100;i++){
      lock.lock();
      try{
        Sout(Thread.currentThread().getName()+"获得锁");
      }finally{
        lock.unlock();
      }
    }
  }
}




一个倒计时的门栓 CountDownLatch


CountDownLatch latch = CountDownLatch( threads.length ); //创建一个length长度的门栓

.await() 阻塞

原join() 当前线程结束自动往前走

.countDown() 原子性–





栅栏工具 CyclicBarrier

循环栅栏工具


// 一个参数:不到20的时候,等待,到了20个,这20个发车,再来的继续等待
CyclicBarrier barrier = new CyclicBarrier(20);
// 两个参数:
CyclicBarrier barrier = new CyclicBarrier(20,run);
run(){ Sout("满员,发车!"); }
//lambdo 表达式
CyclicBarrier barrier = new CyclicBarrier(20()->Sout("满员,发车!"));




同步进行的 Phaser

按照不同的阶段对线程进行划分。


  • 使用场景:

    • 遗传算法
    • 现实生活一步一步执行的场景(如:婚礼)
    • 像是一个一个栅栏一样

  • 使用方法:

    • 自定义一个类,继承 Phaser类;

      static class MarrigePhaser extends Phaser

    • 重写onAdvance方法;(栅栏被推倒的时候自动调用)

      protected boolean onAdvance(int phase,int registeredParties)


  • 方法:

    phaser.arriveAndAwaitAdvance();	//执行结束,开始等待;
    phaser.arriveAndDeregister();	//执行结束,不进入下一阶段;
    




读写锁

程序中的读写锁(一种排他锁、共享锁)


  • 概念

    • A进程在读取ABCD的时候,B进程也来读取ABCD,同时发现A进程在读取,则读取成功;
    • A进程在读取ABCD的时候,B进程来修改ABCD,同时发现A进程在读取,若此时更改ABCD的内容,则A进程读取会出问题,所以修改失败;
    • **总结:**两个都是读取的进程可以同时进行,当有 读 进程在进行时,无法进行 进程,写同理;
  • 作用

    • 避免 / 减少 脏数据
static ReadWriteLoak readWriteLock = new ReentrantReadWriteLock();
//在 ReentrantReadWriteLock 中 分出一个 `readLock`一个`writeLock`
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();

public static void read(Lock lock){
    try{
        lock.lock();
        Thread.sleep(1000);
        Sout("read over!");
        // 模拟读取过程
    }catch(InterruptedException e){
        e.peintStackTrace();
    }finally{
        lock.unlock();
    }
}

public static void write(Lock lock,int a){
    try{
        lock.lock();
        Thread.sleep(1000);
        Sout("write "+ a +"over!");
        // 模拟读取过程
    }catch(InterruptedException e){
        e.peintStackTrace();
    }finally{
        lock.unlock();
    }
}
    
public static void main(String[] args){
    
    Runnable readR = ()->read(lock);    
    //Runnable readR = ()->read(readLock);
    
    Runnable write = ()->write(lock,new Random().nextInt());
    
    for (int i=0;i<18;i++)new Thread(readR ).start();
    for (int i=0;i<2 ;i++)new Thread(writeR).start();
	}
}

// 如果使用 ReentrantLock的话,以上代码在执行的时候也需要等待一秒;

// 解决方法:将Main方法中的锁换成Runnable readR = ()-> read(readLock);





Semaphore 一个有意思的线程池

Semaphore s = new Semaphore(x);x是几则这个 < 线程池 > 就 允许几个线程 同时执行。


public static void main(String[] args){
    Semaphore s = new Semaphore(1);
    //括号中数字为x时,允许x个线程同时执行
    
    // T1 Running
    new Thread(()->{
        try{
            s.acquire();
            // 进来一个进程 1 变成 0 ,别的线程不能执行
            Sout("T1 Running");
            Thread.sleep(200);
            Sout("T1 Running");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            s.release();
            // 离开一个进程 0 变成 1 ,别的线程可以执行
        }
    });
    
        // T2 Running
        new Thread(()->{
        try{
            s.acquire();
            // 进来一个进程 1 变成 0 ,别的线程不能执行
            Sout("T2 Running");
            Thread.sleep(200);
            Sout("T2 Running");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            s.release();
            // 离开一个进程 0 变成 1 ,别的线程可以执行
        }
    });
}

如果x==1则运行结果是T1 T1 T2 T2,否则可能是T1 T2 T1 T2




Exchanger 用于 < ! 两个 ! > 线程交换数据的方法

使用场景:双人游戏中两人交换装备!执行一次就失效,可以循环等待下一次;


public static void main(String[] args){
    // T1
    new Thread(()->{
        String s = "T1";
        try{
            s = sxchanger.exchange(s);
        }cathc(InterruptedException e){
            e.printStackTrace();
        }
        Sout(Thread.currentThread().getName()+""+s);
    },"t1").start();
    
    // T2
    new Thread(()->{
        String s = "T2";
        try{
            s = sxchanger.exchange(s);
        }cathc(InterruptedException e){
            e.printStackTrace();
        }
        Sout(Thread.currentThread().getName()+""+s);
    },"t2").start();
}

线程中有两个变量,分别是ss(局部变量),两个线程同时执行,最后交换T1T2的值;





分布式锁


只是某个类型的锁,将来补充概念。





总结 :


  • 无论何种情况,优先考虑使用synchronized
  • 读写的时候读写锁可以大大提升效率
  • locksynchronized要灵活很多,但是需要自己加锁并释放锁,所以不是很方便

你可能感兴趣的:(Java,并发编程,java)