Java多线程进阶知识梳理(上)

目录

1.什么是JUC并发编程

2.线程和进程

3.Lock(锁)

4.生产者和消费者

5.集合线程不安全

6.Callable接口

7.常用辅助类(必须会)

7.1.CountDownLatch(减法计数器)

7.2.CyclicBarrier(加法计算器)

7.3.Semaphore(信号量)

8.ReadWriteLock(读写锁)

9.阻塞队列(BlockingQueue)

10.同步队列SynchronousQueue



1.什么是JUC并发编程

全称:java.util.conconcurrent

2.线程和进程

进程:一个程序,一个进程往往包含多个线程,至少包含一个

Java默认有几个线程? 2个,main,GC

线程:Thread,Runnable,Callable

Java真的能开启线程吗?开不了,start()方法底层直接调用的是C++(native方法)

并发和并行

并发:多个线程操作同一个资源。

一核CPU,多线程快速交替执行

并行(多个人一起行走)

多核CPU,多线程可以同时执行;线程池

并发编程的本质:充分利用CPU的资源

线程在java中的6中状态

NEW(创建),RUNNABLE(就绪),BLOCKED(阻塞),WAITING(死死等),TIMED_WAITING(超时等),TERMINATED(终止)

在操作系统中的5中状态:创建,就绪,运行,阻塞,终止

wait和sleep区别:

1.来着不同的类

wait  => Object

sleep => Thread

2.关于锁的释放

wait会释放锁,sleep不会释放锁,抱着锁睡觉

3.使用的范围是不同的

wait:必须在同步代码块中使用

sleep:可以在任何地方使用(Thread.sleep()或者TimeUnit.SECONDS.sleep())

3.Lock(锁)

sychronized本质是队列+锁

@Test
	@DisplayName(value = "JUC编程")
	public void test2() throws InterruptedException {
		TicketStation ticketStation = new TicketStation();
		new Thread(() -> {
			for (int i = 0; i < 40; i++) {
				ticketStation.scale();
			}
		}, "A").start();
		
		new Thread(() -> {
			for (int i = 0; i < 40; i++) {
				ticketStation.scale();
			}
		}, "B").start();
		
		new Thread(() -> {
			for (int i = 0; i < 40; i++) {
				ticketStation.scale();
			}
		}, "C").start();
	}

}

class TicketStation {
	private int num = 40;
	
	public synchronized void scale() {
		if(num > 0) {
			System.out.println(Thread.currentThread().getName()+"买了第"+num--+"票,剩余:"+num+"张票");
		}
	}
}

lock锁

Lock lock = new ReentrantLock();

公平锁:十分公平:可以先来后到

class TicketStation {
	private int num = 40;
	
	Lock lock = new ReentrantLock();
	public void scale() {
		
		try {
			lock.lock();
			if(num > 0) {
				System.out.println(Thread.currentThread().getName()+"买了第"+num--+"票,剩余:"+num+"张票");
			}
		} catch (Exception e) {
		} finally {
			lock.unlock();
		}
		
	}
}

非公平锁: 十分不公平:可以插队(默认)

synchronized和lock锁的区别

1.synchronized是内置的关键字,monitorenter和monitorexit这两个原语来实现同步的,Lock是一个java类

2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁

3.synchronized会自动释放锁,Lock必须需要手动是否锁,如果不释放锁,死锁

4.synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁不一定会等待下去(lock.tryLock())

5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,默认非公平(可以自己设置)

6.synchronized适合少量代码同步问题,Lock适合锁大量的同步代码

4.生产者和消费者

传统线程通信同步问题(synchronized版):synchronized+wait/notify

进阶问题:存在虚假唤醒的问题(把if替换成while即可)

@Test
@DisplayName(value = "生产者和消费者")
public void test3() {
    Digit digit = new Digit();
		
    new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"A").start();
	new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"B").start();
	new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"C").start();
	new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"D").start();
}

class Digit {
	int i = 0;
	
	public synchronized void add() {
		while(i > 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		i++;
		System.out.println(Thread.currentThread().getName()+" 生产了"+i);
		//通知消费
		this.notifyAll();
	}
	
	public synchronized void sub() {
		//等待生产
		while(i == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		i--;
		System.out.println(Thread.currentThread().getName()+" 消费了"+i);
		//通知生产
		this.notifyAll();
	}
}

JUC版的通信同步问题:

通过接口condition同步监视器)和lock接口,构成新版的解决多线程通信同步问题的办法

Lock lock = new ReentrantLock();
//同步监视器
Condition condition = lock.newCondition();

@Test
@DisplayName(value = "生产者和消费者")
public void test3() {
    Digit digit = new Digit();
		
    new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"A").start();
    new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"B").start();
    new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"C").start();
    new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"D").start();
}

class Digit {
	int i = 0;
	
	Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	
	public void add() {
		try {
			lock.lock();
			while(i > 0) {
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			i++;
			System.out.println(Thread.currentThread().getName()+" 生产了"+i);
			//通知消费
			condition.signalAll();
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void sub() {
		try {
			lock.lock();
			//等待生产
			while(i == 0) {
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			i--;
			System.out.println(Thread.currentThread().getName()+" 消费了"+i);
			//通知生产
			condition.signalAll();
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

JUC具有精准唤醒的功能

@Test
@DisplayName(value = "Condition精准唤醒")
public void test3() {
	PrintChar printChar = new PrintChar();
	new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printA();},"A").start();
	new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printB();},"B").start();
	new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printC();},"C").start();
}

class PrintChar{
	
	Lock lock = new ReentrantLock();
	Condition condition1 = lock.newCondition();
	Condition condition2 = lock.newCondition();
	Condition condition3 = lock.newCondition();
	
	private int i = 1; //1A 2B 3C
	
	public void printA() {
		try {
			lock.lock();
			while(i != 1) {
				try {
					condition1.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+":AAAAAA");
			i = 2;
			condition2.signal();
		} finally {
			lock.unlock();
		}
	}
	
	public void printB() {
		try {
			lock.lock();
			while(i != 2) {
				try {
					condition2.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+":BBBBBB");
			i = 3;
			condition3.signal();
		} finally {
			lock.unlock();
		}
	}
	
	public void printC() {
		try {
			lock.lock();
			while(i != 3) {
				try {
					condition3.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+":CCCCCC");
			i = 1;
			condition1.signal();
		} finally {
			lock.unlock();
		}
	}
}

八锁问题

synchronized修饰的方法是一个非静态的同步方法,那么锁的是对象,或者说是类实例的调用者,对象不唯一,可以多个new出来

synchronized修饰的方法是一个静态的同步方法,那么锁的是类模板,类模板全局唯一

5.集合线程不安全

不安全的集合在多线程的情况下,普遍会抛出一个异常叫“并发修改异常”,用ArrayList举例

java.util.ConcurrentModificationException

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();
	for (int i = 1; i <= 30; i++) {
		new Thread(() -> {
			list.add(UUID.randomUUID().toString().substring(0, 5));
			System.out.println(list);
		}, String.valueOf(i)).start();
	}
}

java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)

3种解决方法如下:

1.ArrayList线程不安全,可以替换成Vector(最基础的回答)

底层使用的是synchronized锁机制,性能很差

public static void main(String[] args) {
	List<String> list = new Vector<String>();
	for (int i = 1; i <= 30; i++) {
		new Thread(() -> {
			list.add(UUID.randomUUID().toString().substring(0, 5));
			System.out.println(list);
		}, String.valueOf(i)).start();
	}
}

2.Collections集合工具类转换成线程安全集合(稍微提高的回答)

public static void main(String[] args) {
	List<String> list = Collections.synchronizedList(new ArrayList<String>());
	for (int i = 1; i <= 30; i++) {
		new Thread(() -> {
			list.add(UUID.randomUUID().toString().substring(0, 5));
			System.out.println(list);
		}, String.valueOf(i)).start();
	}
}

3.JUC编程CopyOnWriteArrayList(满意的回答)

底层使用的是lock锁机制,在写入时复制,简称COW,是计算机程序设计的一种优化策略,避免造成数据问题

public static void main(String[] args) {
	List<String> list = new CopyOnWriteArrayList<String>();
	for (int i = 1; i <= 30; i++) {
		new Thread(() -> {
			list.add(UUID.randomUUID().toString().substring(0, 5));
			System.out.println(list);
		}, String.valueOf(i)).start();
	}
}

再举个例子:HashSet集合

1.Set<Object> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
2.CopyOnWriteArraySet<Object> copyOnWriteArraySet = new CopyOnWriteArraySet<>();

HashSet的本质是HashMap

HashSet的add()方法:

private static final Object PRESENT = new Object();

PRESENT 没用

再举个例子:HashMap集合

1.Map<String, Object> synchronizedMap = Collections.synchronizedMap(new HashMap<String, Object>());
2.Map<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();

6.Callable接口

  1. 有返回值
  2. 可以抛出异常
  3. 方法不同 call()
public class Test5 {

	public static void main(String[] args) {
		Math math = new Math();
		FutureTask<Integer> futureTask = new FutureTask<>(math);
		
		new Thread(futureTask,"线程1").start();
		new Thread(futureTask,"线程2").start();//适配器
		
		Integer r = futureTask.get();//会阻塞
		System.out.println(r);
	}
	
}

class Math implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		System.out.println("call()");
		return 100;
	}
	
}

FutureTask实现了Runable接口,同时还有包含参数Callable的构造函数

结果执行一次,因为FutureTask有缓存,增强效率

拿去结果,可能会被阻塞

7.常用辅助类(必须会)

7.1.CountDownLatch(减法计数器)

两个方法:

countDownLatch.countDown();//执行一次减1
countDownLatch.await();//等待减到0的时候,被唤醒继续往下执行
public static void main(String[] args) throws InterruptedException {
	CountDownLatch countDownLatch = new CountDownLatch(6);
	for (int i = 1; i <=6; i++) {
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName()+" go out");
			countDownLatch.countDown();
		},String.valueOf(i)).start();
	}
	countDownLatch.await();
	System.out.println("close door");
}

7.2.CyclicBarrier(加法计算器)

方法:

//期待的累计总数,累计总数达到后,会发生“召唤神龙”的事件(Runnable)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙"));

try {
    cyclicBarrier.await();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (BrokenBarrierException e) {
    e.printStackTrace();
}
public static void main(String[] args){
	CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙"));
	for (int i = 1; i <=7; i++) {
		final int temp = i;
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName()+" 得到第"+temp+"颗");
			try {
				cyclicBarrier.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		},String.valueOf(i)).start();
	}

7.3.Semaphore(信号量)

作用:

多个共享资源互斥的使用,并发限流,控制最大线程数

方法:

//限流,有限的资源
Semaphore semaphore = new Semaphore(3);
//资源获取
semaphore.acquire();
//资源释放
semaphore.release();
public static void main(String[] args){
	Semaphore semaphore = new Semaphore(3);
	for (int i = 1; i <= 10; i++) {
		new Thread(()->{
			try {
				semaphore.acquire();
				System.out.println(Thread.currentThread().getName()+ "获取到了车位");
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				semaphore.release();
				System.out.println(Thread.currentThread().getName()+ "离开了车位");
			}
		},"业主"+i).start();
	}
}

8.ReadWriteLock(读写锁)

读的时候可以被多个线程去读,写的时候只能有一个线程去写

读写锁有两个锁

一个叫读锁,也叫共享锁

一个叫写锁,也叫独占锁

public static void main(String[] args){
	ReadWriteTest readWriteTest = new ReadWriteTest();
	for (int i = 0; i < 10; i++) {
		final String temp = String.valueOf(i);
		new Thread(() -> {
			readWriteTest.put(temp, temp);
		}, "线程"+String.valueOf(i)).start();
	}
	
	for (int i = 0; i < 10; i++) {
		final String temp = String.valueOf(i);
		new Thread(() -> {
			readWriteTest.get(temp);
		}, "线程"+String.valueOf(i)).start();
	}
}

class ReadWriteTest{
	
	public Map<String, String> cache = new HashMap<String,String>();
	public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	
	public void put(String key,String value) {
		try {
			readWriteLock.writeLock().lock();
			System.out.println(Thread.currentThread().getName()+"写入"+key);
			cache.put(key, value);
			System.out.println(Thread.currentThread().getName()+"写入OK"+key);
		} finally {
			readWriteLock.writeLock().unlock();
		}
	}
	
	public void get(String key) {
		try {
			readWriteLock.readLock().lock();
			cache.get(key);
			System.out.println(Thread.currentThread().getName()+"读了"+key);
		} finally {
			readWriteLock.readLock().unlock();
		}
	}
}

执行的结果一定是单线程写,多行程读

9.阻塞队列(BlockingQueue)

queue接口下包含阻塞队列(BlockingQueue),非阻塞队列(AbstractQueue),双端队列(BlockingDeque,Deque)

Iterable->Collection->Queue->BlockingQueue->{ArrayBlockingQueue,LinkedBlockingQueue }

应用场景:多线程并发处理,线程池

BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

四组API(必须得会)

(会抛出异常)插入,删除,检测队首元素: add,remove,element,一旦元素不存在或者空间满,则抛出异常

(不会抛出异常)插入,删除,检测队首元素:offer(),poll,peek,一旦元素不存在或者空间满,则不会抛出异常,返回null

(不会抛出异常,但等待(一直阻塞))插入,删除:put,take

(不会抛出异常,但等待(超时阻塞))插入,删除:offer(E e, long timeout, TimeUnit unit),poll(long timeout, TimeUnit unit)

10.同步队列SynchronousQueue

  • 和其他的BlockingQueue 不一样,SynchronousQueue不存储元素
  • put了一个元素,必须从里面先take取出来,否则不能再put进去值了
  • 每个插入操作必须等待另一个线程相应的删除操作

public static void main(String[] args){
		SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
		
		new Thread(() -> {
			try {
				synchronousQueue.put("aabbcc");
				System.out.println(Thread.currentThread().getName()+" put le aabbcc");
				synchronousQueue.put("112233");
				System.out.println(Thread.currentThread().getName()+" put le 112233");
				synchronousQueue.put("aqaqaq");
				System.out.println(Thread.currentThread().getName()+" put le aqaqaq");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		},"ThreadA").start();
		
		new Thread(() -> {
			try {
				TimeUnit.SECONDS.sleep(2);
				System.out.println(synchronousQueue.take());
				TimeUnit.SECONDS.sleep(2);
				System.out.println(synchronousQueue.take());
				TimeUnit.SECONDS.sleep(2);
				System.out.println(synchronousQueue.take());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		},"ThreadB").start();
	}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼岸花@开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值