文章目录
- 1、什么是J.U.C
- 2、进程、线程、协程
- 2.1 简介
- 2.2 线程有几个状态
- 2.3 wait和sleep的区别
- 3、Lock锁(重点)
- 3.1 简介
- 3.2 lock锁和synchronized有什么区别
- 4、生产者和消费者问题
- 4.1 简介
- 4.2 使用synchronized实现的demo
- 4.3使用Lock实现的demo
- 5、锁的是谁问题
- 1、两个线程访问同一个对象的同步方法 (会发生互斥)
- 2、两个线程访问两个对象的同一同步方法 (不会发生互斥)
- 3、两个线程访问synchronized修饰的静态方法 (会发生互斥)
- 4、两个线程访问同步方法和非同步方法 (不会互斥)
- 5、两个线程访问同一个对象的不同的普通同步方法 (会发生互斥)
- 6、两个线程同时访问同一类下不同的静态synchronized方法 (互斥)
- 6、集合不安全类
- 1、ArrayList(线程不安全)
- 2、Set(线程不安全)
- 3、Map(线程不安全)
- 7、Callable
- 8、常用辅助类
- 1、CountDownLatch
- 2、CyclicBarrier
- 3、Semaphore
1、什么是J.U.C
JUC全称就是 Java.util.concurrent,我们去jdk1.8帮助文档看看
2、进程、线程、协程
2.1 简介
进程:是应用程序的启动实例,进程拥有打开代码和打开的文件资源、数据资源和独立的内存空间,它是资源分配的基本单位
线程:它是程序的实际执行者,一个进程可以有多个线程,但至少有一个,线程拥有自己的栈空间,它是调度分配的基本单位
协程:它是一种更轻量级的存在,正如进程有多个线程,线程也有多个协程
Java默认有几个线程? 2个:Main线程、GC线程
Java真的能开启线程吗? 不能,其实Java调用的start()方法底层又是去调用一个start0()的本地方法去开启的线程,Java是没有权限开启线程的
理解并行和并发的概念
- 并行:(多个人一起行走)它是在多核下的:真正意义上的同时执行;线程池
- 并发:(多线程操作同一资源)单核下:能营造出同时执行的假象,其实是线程交替执行的,这种交替是非常快的
并发编程的本质:就是充分利用CPU的资源
2.2 线程有几个状态
这个概念是混的,从操作系统角度来讲:线程拥有5种状态,分别是:新建、可执行、运行、阻塞、死亡;从Java角度来讲:进入底层代码发现,它有6个枚举状态,分别是:NEW(新建)、RUNNABLE(运行)、WAITING(等待,傻傻的等)、TIMED_WAITING(超时等待,有时间)、BLOCKED(阻塞)、TERMINATED(终止)
2.3 wait和sleep的区别
wait | sleep | |
---|---|---|
1、来自不同的类 | Object | Thread |
2、关于锁的释放 | 释放锁(进入等待锁池,等待notify的唤醒) | 不释放锁(可以理解为抱着锁睡觉,一段时间和可以醒来继续执行) |
3、使用范围不同 | 必须在同步代码块中 | 可以在任何地方睡 |
4、方法属性 | 实例方法 | 静态方法 |
3、Lock锁(重点)
3.1 简介
我们先看下jdk下的lock包下有什么实现类
锁从获取资源的公平性角度来讲可分为公平锁和非公平锁,从乐观和悲观方面分为乐观锁和悲观锁,从是否共享资源角度来讲可分为共享锁和独占锁,从锁的状态角度分为偏向锁、轻量级锁和重量级锁
3.2 lock锁和synchronized有什么区别
- synchronized是Java的关键字,lock是一个接口,它有很多实现类,如ReentrantLock,所以lock的扩展性更高
- synchronized是隐式的加锁解锁,lock是显示的加锁解锁,操作失误的话有可能会产生死锁
- synchronized在获取锁的过程中,如果获取不到,将会一直处于阻塞状态,lock不一定等待下去
- synchronized无法判断获取锁的状态,lock可以得到获取锁的状态
- synchronized是可重入锁,不可以中断的,非公平的,lock是可重入锁,可以判断获取锁的状态,默认是非公平的(可以设置)
- synchronize适合锁少量的代码问题,lock适合锁大量的代码问题
4、生产者和消费者问题
4.1 简介
线程间是如何通信的呢?最经典的例子就是生产者/消费者问题
4.2 使用synchronized实现的demo
有个小插曲 if和where 如果资源中使用if判断的话,可能会出现虚假唤醒的情况
package com.markus.pc;
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();
new Thread(()->{
for (int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
while(number!=0){
//等待
this.wait();
}
//业务逻辑
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我+1完毕了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number==0){
//等待
this.wait();
}
//业务逻辑
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//唤醒其他线程
this.notifyAll();
}
}
4.3使用Lock实现的demo
package com.markus.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data1 data = new Data1();
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();
new Thread(()->{
for (int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data1{
private int number = 0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();//精准的通知和唤醒线程
public void increment() throws InterruptedException {
lock.lock();
try {
if(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{
if(number==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
condition.signalAll();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
syn的替代方案
syn+wait+notify完全可以用Lock+await()+signalAll()来替换,但技术的更新肯定不是只是替换的作用,还会与改进,就比如 Condition 可以来实现精准唤醒
package com.markus.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 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10 ; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10 ; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10 ; i++) {
data.printC();
}
},"C").start();
}
}
class Data3{
private int number = 1; //1A执行 2B 3C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try{
while(number!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"-> A");
number = 2;
condition2.signal();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
while(number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"-> B");
number = 3;
condition3.signal();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"-> C");
number = 1;
condition1.signal();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
5、锁的是谁问题
希望通过这几个例子,能让大家明白,锁的是谁
1、两个线程访问同一个对象的同步方法 (会发生互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
class Phone{
public synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
}
2、两个线程访问两个对象的同一同步方法 (不会发生互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
Phone1 phone2 = new Phone1();
new Thread(()->{
phone1.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
class Phone1{
public synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
}
3、两个线程访问synchronized修饰的静态方法 (会发生互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test3 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
class Phone2{
public static synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
}
4、两个线程访问同步方法和非同步方法 (不会互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test4 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
//Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.hello();
},"B").start();
}
}
class Phone3{
public synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
public void hello(){
System.out.println(Thread.currentThread().getName());
System.out.println("hello: "+Thread.currentThread().getName());
}
}
5、两个线程访问同一个对象的不同的普通同步方法 (会发生互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test5{
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
//Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.hello();
},"B").start();
}
}
class Phone4{
public synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
public synchronized void hello(){
System.out.println(Thread.currentThread().getName());
System.out.println("hello: "+Thread.currentThread().getName());
}
}
6、两个线程同时访问同一类下不同的静态synchronized方法 (互斥)
package com.markus.lock8;
import java.util.concurrent.TimeUnit;
public class Test6 {
public static void main(String[] args) {
Phone5 phone1 = new Phone5();
Phone5 phone2 = new Phone5();
new Thread(()->{
phone1.sendSMS();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.hello();
},"B").start();
}
}
class Phone5{
public static synchronized void sendSMS(){
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信: "+Thread.currentThread().getName());
}
public static synchronized void hello(){
System.out.println(Thread.currentThread().getName());
System.out.println("hello: "+Thread.currentThread().getName());
}
}
6、集合不安全类
1、ArrayList(线程不安全)
在单线程情况下,ArrayList是没有问题的,但是在多线程环境下,会出现异常:ConcurrentModificationException
package com.markus.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
//并发情况下,ArrayList是线程不安全的,会出现ConcurrentModificationException异常
//我们如何去保证它线程安全呢?
//1、使用vector
//2、使用Collections.synchronizedList(list),封装,底层是维护的一个Object对象,对链表操作的时候对这个对象加锁
//3、使用J.U.C下的CopyOnWriteArrayList;
// List<String> list = new ArrayList<>();
// List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
我们来看看CopyOnWriteArrayList底层,顾名思义:写时复制,读的时候不影响,当在写的时候创建一个副本去写,写完之后将旧数组指向新数组。它是实现了一个ReentrantLock对象来进行加锁解锁,然后维护了一个volatile的array对象数组
2、Set(线程不安全)
HashSet的底层其实就是一个HashMap,利用HashMap的key进行存值,它是线程不安全的
package com.markus.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) {
//线程不安全 出现 ConcurrentModificationException
// 1、通过Collections的同步set来实现线程安全
// 2、通过 J.U.C下的CopyOnWriteArraySet来实现线程安全
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for(int i = 0 ; i < 30 ; i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3、Map(线程不安全)
package com.markus.unsafe;
import java.util.Collections;
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) {
//并发情况 出现 ConcurrentModificationException
// 1、通过Collections.synchronizedMap();解决
// 2、通过ConcurrentHashMap解决
// Map<String,String> map = new HashMap<>();
// Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
Map<String,String> map = new ConcurrentHashMap<>();
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();
}
}
}
7、Callable
1、有缓存
2、结果可能会等待
package com.markus.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() 只认识 Runnable
//new Thread(Runnable) Runnable有一个实现类:FutureTask
//new Thread(FutureTask) FutureTask认识Callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask).start();
new Thread(futureTask).start();//按常理说它应该执行两次call,但是实际执行只有一次,结果会被缓存,提高效率
Integer i = (Integer) futureTask.get();//它会阻塞,如果call方法是一个耗时操作,它会一直阻塞到返回返回值
//可采用异步通信的方式解决
System.out.println(i);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
8、常用辅助类
1、CountDownLatch
原理:
- countDown() 计数器-1
- await() 等到计数器为0的话,会被唤醒,继续向下执行
package com.markus.add;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; 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");
}
}
2、CyclicBarrier
可以理解为一个加法器,所有线程等待屏障,直到所有行都被处理
package com.markus.add;
import com.markus.pc.C;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{
System.out.println("西天取经成功");
});
for (int i = 1; i <= 3; i++) {
final int temp = i;
// lambda能操作到 i 吗?
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"收徒"+temp+"个徒弟");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3、Semaphore
原理:
semaphore.acquire():获得,如果已经满了,就需要等待,直到被释放为止
semaphore.release():释放,会将当前的信号量释放 +1,然后唤醒等待的线程
作用:多个共享资源的互斥的使用!并发限流,控制最大线程数
package com.markus.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//模拟 停车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();//获取车位
System.out.println(Thread.currentThread().getName()+"得到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}