JUC并发编程
多任务:一边吃饭一边睡觉

线程(thread):本质上是程序里不同的执行路径
进程(process):由不同的线程组成
一、使用多线程的方法
1.实现 Runnable 接口
先继承Runnable接口创建一个子线程
public class MyRunnable implements Runnable { @Override public void run() { // ... } }
在主线程中使用Thread启动线程
public static void main(String[] args) { MyRunnable instance = new MyRunnable(); Thread thread = new Thread(instance); thread.start(); }
2.实现Callable接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装
public class MyCallable implements Callable<Integer> { public Integer call() { return 123; } }
public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread thread = new Thread(ft); thread.start(); System.out.println(ft.get()); }
3.继承Thread类
Thread 类也实现了 Runable 接口,所以需要实现 run() 方法
public class MyThread extends Thread { public void run() { // ... } }
public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); }
由于java不支持多继承,但是能实现多个接口,所以尽量用接口做多线程
二.线程的状态
Thread.State()中定义了6个状态
public enum State { NEW, // 新建,start之前 RUNNABLE, // 运行 BLOCKED, // 阻塞,等待获得锁 WAITING, // 等待,调用了Oject.wait(),Thread.join(),LockSupport.park() TIMED_WAITING, // 限时等待,调用了Thread.sleep(time),Oject.wait(time),Thread.join(),LockSupport.parkNanos(),LockSupport.parkUntil()
TERMINATED; // 终止,线程结束或产生异常
}
锁的分类:
1. 乐观锁,悲观锁 :乐观锁认为获取锁的可能性较大,首先尝试获得,如果获得不了就去排队,悲观锁直接排队
2. 独占锁,共享锁:一个典型的例子是读写锁,读的时候允许多个线程访问,写的时候只允许一个线程
3. 可重入锁,不可重入锁:如果一个方法加了锁,其中又调用了另一个加锁的方法,可重入锁允许调用,不重入锁不允许, synchronized是可重入锁。
4. 公平锁,非公平锁:公平锁要求所有线程排队,非公平锁不要求
synchronized,volitile和cas的底层实现完全一样
cas的c++底层代码是lock cmpxchg这条汇编指令,cmpxchg(compare and exchange)并不能保证原子性,因为这条指令的执行过程是拿到值、对值加减、比较值和拿的时候比有无变化,没有变化的话对值修改。如果在比较前值被修改了,这条指令可以起作用,但是如果在比较过程中,对值修改前,值被其他线程修改,这条指令是不能发现的。这个时候靠lock指令保证原子性,lock指令的作用是拿到值到最终修改前,这个过程不允许被打断。
在了解synchronize的原理前需要了解对象布局:
一个对象的内存布局由三个部分组成

对象头:包括markword和类型指针,实例数据是对象中的字段,如果对象头加实例数据的大小是8字节的整数倍,由对齐填充部分补齐。
对于Object o = new Object();这条命令,首先在堆里划分一块内存装Object对象,栈里创建一个引用o指向这个对象,这个很简单,关键是堆内存里的Object有什么,可以通过在maven里添加JOL(java object layout)依赖查看对象的内存布局:

可以看到前12字节是对象头,因为没有字段,所以没有实例数据,对齐填充用4个空字节把对象补齐成16位。
如果对o上锁:synchronize(o)重新查看内存布局

可以看到对象头的markword发生了变化,说明锁是加在对象头的markword上的。
实际上markword记录了synchronize锁的升级过程
synchronized从代码到底层实现:1.java代码中使用synchronized2.字节码中会加入monitorenter和monitorexit,线程在获取锁的时候,实际上就是获得一个监视器对象(monitor) ,monitor 可以认为是一个同步对象,线程运行到monitorenter时会尝试获取对象的monitor所有权。3.执行过程中自动升级。4.汇编层使用lock comxchg
synchronized的锁升级:1.无锁2.偏向锁3.自旋锁4.重量级锁。
自旋锁和重量级锁的适用场景:由于自旋锁消耗cpu,所以自旋锁适合锁同步的代码块执行速度快且锁竞争不激烈的情况,重量级锁适合锁同步的代码块执行速度慢且锁竞争激烈的情况
volatile解决指令重排序的过程:1.java代码中对变量使用volatile。2.字节码中添加ACC_VOLATILE。3.JVM使用内存屏障,包括LoadLoad,LoadStore,StoreStore,StoreLoad,Load是读操作,Store是写操作,LoadLoad是指两个在两个读操作之间加屏障,使它们不能交换顺序,其他三个同理。在对volatile进行写操作时,在这条指令前加StoreStore,在后面加StoreLoad,表示全部写完之后才能读。在对volatile进行读操作时,在这条指令前加LoadLoad,在后面加LoadStore,表示全部读完之后才能写。4.hotspot实现:还是用lock

三、AQS
1.CountDownLatch
CountDownLatch是一个倒计数器,用于某个线程等待特定数量的线程执行完毕。但是只能用一次
public class CountdownLatchTest { private static final int num = 10; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(num); // 设置CountDownLatch初始值 ExecutorService executorService = Executors.newFixedThreadPool(10); for(int i = 0; i < num; i++){ executorService.execute(() -> {System.out.println("run"); countDownLatch.countDown(); // 初始值减1 }); } countDownLatch.await(); // 如果减到0,执行后边的程序 System.out.println("end"); executorService.shutdown(); } }
2.CyclicBarrier
与CountDownLatch功能类似,但是又几点不同:1.CountDownLatch只能用一次,CyclicBarrier可以通过reset()方法重复使用2.CyclicBarrier.await()方法在检查计数器时,如果大于零会自动减1,因此CyclicBarrier不需要countDown方法。3.基于第二点,CountDownLatch与CyclicBarrier相比,CountDownLatch可以人为控制,在一个线程里可以使用多次countDown方法,使计数器每次减去大于1的值。
public class CyclicBarrierTest { public static void main(String[] args){ final int num = 10; CyclicBarrier cyclicBarrier = new CyclicBarrier(num); ExecutorService executorService = Executors.newFixedThreadPool(num); for(int i = 0; i<num; i++){ executorService.execute(()->{ System.out.println("before"); try { cyclicBarrier.await(); // 检查计数器并减1 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("after"); }); } } }
3.Semaphore
信号量,用于限流,控制同时运行的线程数量,设置一个信号量最大值,线程通过acquire方法获得一个信号量并使信号量减1,线程运行完毕后通过release方法释放信号量
public class SemaphoreTest { public static void main(String[] args){ final int maxThreadNum = 3; // 最多允许三个线程同时运行 final int curThreadNum = 10; // 共有10个线程 ExecutorService executorService = Executors.newFixedThreadPool(curThreadNum); Semaphore semaphore = new Semaphore(maxThreadNum); for(int i = 0; i < curThreadNum; i++){ executorService.execute(()->{ try { semaphore.acquire(); // 获取信号量,如果没有获取成功则等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(semaphore.availablePermits()+" "); semaphore.release(); // 执行完毕,释放信号量 }); } executorService.shutdown(); } }
浙公网安备 33010602011771号