public class Main { public static void main(String[] args) { Data data = new Data(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printA();}).start(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printB();}).start(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printC();}).start(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printA();}).start(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printB();}).start(); new Thread(() -> {for (int i = 0; i < 10; i++) data.printC();}).start(); }}/** * A执行完调用B,B执行完调用C,C执行完调用A */class Data { //一个判断的标志位,1A,2B,3C private int num=1; Lock lock = new ReentrantLock(); Condition condition1 =lock.newCondition(); Condition condition2 =lock.newCondition(); Condition condition3 =lock.newCondition(); public void printA(){ lock.lock(); try { while (num!=1){ condition1.await(); } System.out.println("A"); //唤醒指定的人 condition2.signal(); num=2; } catch (Exception e) { } finally { lock.unlock(); } } public void printB(){ lock.lock(); try { while (num!=2){ condition2.await(); } System.out.println("B"); //唤醒指定的人 condition3.signal(); num=3; } catch (Exception e) { } finally { lock.unlock(); } } public void printC(){ lock.lock(); try { while (num!=3){ condition3.await(); } System.out.println("C"); //唤醒指定的人 condition1.signal(); num=1; } catch (Exception e) { } finally { lock.unlock(); } }}
公平锁和非公平锁
向可重入锁的构造中传入true就是公平锁,传入false就是非公平锁(默认)
公平锁可以先来后到,一定要排队
非公平锁可以插队
可重入锁
在下面例子中,加锁了两次,已经获得锁的对象可以继续给他加锁而不会发生死锁,这就是可重入锁
而且这里可重入锁必须是配套的,有几个加锁就要有几个解锁,不然就可能发生死锁
java
public class Main { public static void main(String[] args) { A a = new A(); new Thread(()->{ a.methodA(); }).start(); }}class A{ private Lock lock = new ReentrantLock(); public void methodA(){ lock.lock(); //做一些业务 methodB(); lock.unlock(); } public void methodB(){ lock.lock(); //做一些业务 lock.unlock(); }}
自旋锁
java
public class MyLock { //原子引用的是线程 private AtomicReference<Thread> lock=new AtomicReference<>(null); public void lock(){ //期望值是空的,所以第一次能通过,后面就不能通过了,在这里自旋 //这个我写的锁貌似是不了可以重入的,只能锁一次 while(!lock.compareAndSet(null,Thread.currentThread())){ } } public void unlock(){ //锁的时候是啥线程,现在就只能啥线程来解锁 lock.compareAndSet(Thread.currentThread(),null); }}//至于感觉好像用if判断也可以实现?//错!if操作不满足原子性,在并发场景下不安全,所以这里才采用原子引用(满足原子性)
测试
java
public class Main { public static void main(String[] args) { A a = new A(); new Thread(()->{ a.run(); }).start(); new Thread(()->{ a.run(); }).start(); }}class A{ private MyLock lock = new MyLock(); public void run(){ System.out.println(Thread.currentThread().getName()+" beforeLock"); lock.lock(); System.out.println(Thread.currentThread().getName()+"run"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } lock.unlock(); }}
集合类不安全
java
//并发下list不安全List list = new ArrayList();for (int i = 0; i < 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(list); }).start();}
并发修改异常
怎么变安全?
Vector取代ArrayList,因为它是线程安全的
缺点:Vector是古老类,效率不太行
使用工具类转换为线程安全的
JUC下有一些线程安全的类
//CopyOnWrite 写入时复制 COW 一种优化策略/多线程调用的时候,list,读取的时候,固定的,写入(覆盖)//再写入的时候避免覆盖造成数据问题//读写分离List list = new CopyOnWriteArrayList();// CopyOnWriteArrayList 使用了 lock 锁,所以效率比使用了 synchronized 的 vector 效率更高
public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> task = new FutureTask<>(new Data()); new Thread(task).start(); task.get(); }}class Data implements Callable<String> { @Override public String call() throws Exception { return "Hello World"; }}
常见的辅助类
CountDownLatch
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await调用立即返回。 这是一个一次性的现象 - 计数无法重置。
java
public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(countDownLatch::countDown).start(); } countDownLatch.await();//等待计数器归零,然后才能向下执行 System.out.println("aaa");}
public class Main { public static void main(String[] args) throws Exception { Data data = new Data(); for (int i = 0; i < 5; i++) { new Thread(data::put).start(); } for (int i = 0; i < 5; i++) { new Thread(data::get).start(); } }}class Data { private volatile Map map=new HashMap<>(); private ReadWriteLock lock=new ReentrantReadWriteLock(); public void put(){ lock.writeLock().lock(); System.out.println(Thread.currentThread().getName()+"正在进行写入"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"写入完成"); lock.writeLock().unlock(); } public void get(){ lock.readLock().lock(); System.out.println(Thread.currentThread().getName()+"正在进行读取"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"读取完成"); lock.readLock().unlock(); }}
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 5,5,200,TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
//没有加volatile关键字,不可见,那个线程一直无法关闭public class Main { public static Integer i=1; public static void main(String[] args) throws Exception { new Thread(()->{while (i.equals(1));}).start(); TimeUnit.SECONDS.sleep(5); i=0; System.out.println("i=0"); }}//加了关键字之后可以正常了public class Main { public volatile static Integer i=1; public static void main(String[] args) throws Exception { new Thread(()->{while (i.equals(1));}).start(); TimeUnit.SECONDS.sleep(5); i=0; System.out.println("i=0"); }}//但是,如果使用了volatile关键字就不一样了:*第一,valatile关键字会强制将修改过后的值立即写入主存;*第二,使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);那么这样的话,线程1读取到的肯定就是最新的值了。