JUC多线程
什么是JUC:源码+官方文档,面试高频问!
Runnable 、callable
1.线程和进程
进程:一个程序,QQ.exe等程序的集合,一个进程可以包含多个线程,至少包含一个!
java默认两个线程:2个,main、GC
线程:Thread,Runnable、Callable
2.回顾多线程
java是否可以开启线程? 答:不可以!
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法,底层的C++,java无法直接操作硬件
private native void start0();
并发:多线程操作同一资源(CPU单核情况下,快速交替)
并行:CPU多核,多个线程可以同时执行,线程池
package com.lsh;
public class demo01 {
public static void main(String[] args) {
// new Thread().start();
//获取CPU的核数
//cpu密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待,死死地等
WAITING,
//超时等带
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep区别
1.来自不同的类
wait来自于Object
sleep来自于Thread
2.关于锁的释放
wait会释放锁,sleep抱着锁睡觉,不释放锁
3.使用的范围不同
wait必须在同步代码块中
sleep可以在任何地方睡
3.传统的Synchronized锁
package com.lsh;
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类OOP
class Ticket{
//属性、方法
private int number = 40;
//买票的方式
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+":卖出了第"+(number--)+"张票,剩余"+(number));
}
}
}
4.Lock锁
两个方法:加锁(lock)和解锁(unlock)
公平锁:可以先来后到
非公平锁:可以插队
package com.lsh;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
}
}
//资源类OOP
//lock三部曲:
//1.new ReentrantLock();
//2.lock方法
//3.unClock方法
class Ticket2{
//属性、方法
private int number = 40;
Lock lock = new ReentrantLock();
//买票的方式
public synchronized void sale(){
lock.lock();
try {
if(number>0){
System.out.println(Thread.currentThread().getName()+":卖出了第"+(number--)+"张票,剩余"+(number));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
5.Synchronized锁和Lock锁区别
1.synchronized 内置的java关键字,Lock是java类
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized会自动释放锁,lock必须要手动释放锁,如果不释放锁,死锁
4.synchronized 线程个(获得 锁,阻塞)、线程2(等待、傻傻的等);Lock锁就不一定会等待下去;
5.sunchronized 可重入锁,不可以终端,非公平;
Lock 可重入锁,可以判断锁,非公平(可以自己设置)
6.synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码
6.传统的消费者和生产者问题、防止虚假唤醒
package com.lsh.pc;
/**
* 线程之间的通信问题,生产者和消费者问题
* 线程交替执行 A,B操作同一个变量 num=0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待、业务、通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException{
if(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=========>"+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException{
if(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=========>"+number);
this.notifyAll();
}
}
问题存在:多个线程时
虚假唤醒解决方式:将if改为while判断即可
7.Lock版生产者和消费者问题
通过Lock找到Condition
Lock版消费者生产者模式
package com.lsh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题,生产者和消费者问题
* 线程交替执行 A,B操作同一个变量 num=0
* A num+1
* B num-1
*/
public class B {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待、业务、通知
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException{
lock.lock();
try {
while (number!=0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=========>"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException{
lock.lock();
try {
while (number==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=========>"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}
8.Condition实现精准通知唤醒
package com.lsh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{//资源类,lock锁
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
//业务-->判断-->执行-->通知
while(number!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=====>"+"AAAAAAAAAA");
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=====>"+"BBBBBBBBBB");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=====>"+"CCCCCCCCC");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
9.八锁现象彻底理解锁
对象和class
第一组
package com.lsh.pc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:锁的八个问题
* 1.标准情况下,两个线程是先打印 打电话还是发短信 //1.发短信 2.打电话
* 2.发短信方法延迟4s,两个线程先打印 打电话还是发短信 //1.发短信 2.打电话
*
*/
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMes();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//锁的对象是方法的调用者
//两个方法用到的是同一把锁,谁先拿到谁执行
public synchronized void sendMes(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
第二组
package com.lsh.pc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:锁的八个问题
* 3.增加一个普通方法,hello()//先执行普通方法
* 4.测试两个对象,先调用谁 //先打电话,后发短信(睡了4s),锁的不同对象,相互之间不影响
*/
public class Test02 {
public static void main(String[] args) {
//测试两个对象,先调用谁
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendMes();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
//锁的对象是方法的调用者
//两个方法用到的是同一把锁,谁先拿到谁执行
public synchronized void sendMes(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//没有锁,普通方法
public void hello(){
System.out.println("hello");
}
}
第三组
package com.lsh.pc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:锁的八个问题
* 5.静态方法 //先打印发短信,再打印打电话
* 6.两个对象 //还是跟一个对象时一样,锁的是类(模板)
*/
public class Test03 {
public static void main(String[] args) {
//测试两个对象,先调用谁
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendMes();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone3{
//锁的对象是方法的调用者
//两个方法用到的是同一把锁,谁先拿到谁执行
//static 类一加载就锁上了,锁模板
public static synchronized void sendMes(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
第四组
package com.lsh.pc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:锁的八个问题
* 7.1个静态的同步方法,1个普通的同步方法, 同一个对象? //先打印打电话,再打印发短信
* 8.2个静态的同步方法,1个普通的同步方法,不同的对象? //先打印打电话,再打印发短信
*/
public class Test04 {
public static void main(String[] args) {
//测试两个对象,先调用谁
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendMes();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone4{
//锁的对象是方法的调用者
//两个方法用到的是同一把锁,谁先拿到谁执行
//static 类一加载就锁上了,锁模板
public static synchronized void sendMes(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
总结:分辨出具体锁的东西,是类模板还是具体的对象
普通方法:锁对象,方法调用者
static修饰方法:锁类模板
10.CopyOnWriteArrayList
package com.lsh.pc.unsafe;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下ArrayList不安全的
/**
* 解决方案:
* 1.List<String> list = new Vector<>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*
*
*/
//CopyOnWrite cow 写入时复制,计算机程序设计领域的一种优化策略
//在写入的时候避免覆盖,造成数据问题
//读写分离
//copyOnWriteArrayList比Vector牛逼在哪里?
/**
* vector使用synchronized方法,导致性能差
* copyOnWriteArrayList使用Lock锁,性能好
* copyOnWriteArrayList源码
* public boolean add(E e) {
* final ReentrantLock lock = this.lock;
* lock.lock();
* try {
* Object[] elements = getArray();
* int len = elements.length;
* Object[] newElements = Arrays.copyOf(elements, len + 1);
* newElements[len] = e;
* setArray(newElements);
* return true;
* } finally {
* lock.unlock();
* }
* }
*
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
11.CopyOnWriteArrayMap
1.CopyOnWriteArraySet
package com.lsh.pc.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 同理可证:java.util.ConcurrentModificationException
*/
public class SetTest {
public static void main(String[] args) {
//Set<String> set = new HashSet<>();
//1.解决并发不安全问题
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
//2.并发包下的类
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HaseSet底层原理:
public HashSet() {
map = new HashMap<>();
}
//add set 本质就是map key是无法重复的(map的key和value可以为空,key为null只有一种情况)
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
**HashMap **
package com.lsh.pc.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
public static void main(String[] args) {
//new HashMap<>(16,0.75)
//java.util.ConcurrentModificationException
//1.new ConcurrentHashMap<>()
Map<String,String> map = new ConcurrentHashMap<>();
//Map<String,String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
12.走进Callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run()/call()
package com.lsh.pc.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<>()).start();
//new Thread(new FutureTask<>(Callable)).start();
// new Thread().start();
//适配类 FutureTask
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
Integer a = (Integer)futureTask.get();
System.out.println(a);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
常用的辅助类(13,14,15)
13.CountDownLatch
package com.lsh.add;
import java.util.concurrent.CountDownLatch;
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
//总数是6
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go Out");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器为0,再向下执行
System.out.println("Close Door");
}
}
countDawnLatch.countDown();countDownLatch.countDown();//数量-1
countDownLatch.await();//等待计数器为0,再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行!
14.CyclicBarrier
package com.lsh.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集第"+temp+"龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
15.Semaphore
package com.lsh.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
private final static int THREAD_COUNT = 6;
//private static ExecutorService threadPool= Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(3);
public static void main(String[] args) {
for (int i = 1; i < THREAD_COUNT; i++) {
new Thread(()->{
try {
s.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(1);
System.out.println((Thread.currentThread().getName() + "离开车位"));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();//释放
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire()获得,假设满了,等待,等待被释放为止;
semaphore.release()释放,会将当前的信号量+1, 然后唤醒等待的线程
作用:多个共享资源的互斥,并发限流,控制最大的线程数