1、什么是JUC
说白了就是三个包

业务:普通的线程代码 Thread
Runnable接口 没有返回值 效率相比于Callable相对较低
2、进程与线程
线程、进程
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程?两个:main线程,GC线程(垃圾回收)
对于Java而言:Thread、Runnable、Callable
Java真的可以开启线程吗? 开不了的
Java实际上调用了本地方法,底层的C++,Java无法操作硬件,因为Java是运行在虚拟机上的
private native void start0();
并行、并发
并发(多个线程操作同一个资源,微观上是依次执行的)
并行(多个线程真正的同时执行);线程池
并发编程的本质:充分利用CPU的资源
线程有几个状态?6个
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待(阻塞)
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep 区别
1、来自不同的类
wait => Object
sleep => Thread
企业开发不使用sleep,使用的是TimeUnit
2、关于锁的释放
wait方法会释放锁
sleep不会释放锁
3、使用的范围是不同的
wait必须在同步代码块中才能使用
sleep可以在任何地方执行(睡)
4、是否需要捕获异常
wait不需要捕获异常(会抛出中断异常)
sleep必须要捕获异常
3、Lock锁(重点)
传统 Synchronized 锁
//基本的卖票例子
/**
* 真正的多线程开发(企业开发)不会在资源类去实现Runnable接口
* 线程就是一个单独的资源类,它没有任何的附属操作
* 1、属性 2、方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发,多个线程操作同一个资源类
Ticket ticket = new Ticket();
//怎么操作资源类:把资源丢入线程
new Thread(()->{
for (int i = 1; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类,OOP编程(降低耦合性)
class Ticket{
//属性
private int number = 50;
//方法
//synchronized 本质:队列 、锁
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"张票,剩余"+number);
}
}
}
JUC 版 Lock锁
公平锁:十分公平,必须先来后到
非公平锁:十分不公,可以插队(默认)因为正常情况下,线程存在优先级
public class SaleTicketDemo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"A").start();
new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"B").start();
new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"C").start();
}
}
//Lock锁
//1、new ReentrantLock();
//2、lock.lock();//加锁
//3、finally -> lock.unlock();//解锁
class Ticket2{
private int number = 50;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();//加锁
try {
//业务代码
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"张票,剩余"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
Synchronized 和 Lock 的区别
1、Synchronized 内置的关键字;Lock是以一个Java类
2、Synchronized 无法判断获取锁的状态;Lock 可以判断是否获得到了锁
3、Synchronized 会自动释放锁;Lock必须要手动释放锁!如果不释放锁就会 死锁
4、Synchronized 线程1(获得锁 -> 阻塞)=> 线程2(等待 -> 一直等待);Lock就不一定会等待下去
5、Synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,可以设置公平/非公平
6、Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的代码同步问题
锁是什么?如何判断锁的是谁?
4、生产者消费者问题
业务代码:判断->执行->通知
Synchronized版
注意 wait
、notifyAll
前别忘了加this
/**
* 线程之间的通信问题:生产者和消费者问题
* 线程交替执行 A B 操作统一个变量 A+1,B-1
* 等待wait 通知notifyAll(别忘了加this)
*/
public class A {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
}
}
class Date{
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){
this.wait();//等待
}
number++;//业务
System.out.println(number);
this.notifyAll();//通知其他线程(唤醒其他线程)
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println(number);
this.notifyAll();
}
}
出现问题!四个线程同时操作时,出现同时加1,结果为2、3情况;虚假唤醒问题
问题出现原因:使用的if
判断只会判断一次
if (number!=0){
this.wait();//等待
}
解决方案:改为 while
方法
解决原理:因为在减1之后会唤醒所有线程(不止一个),而这里有两个生产者,所以这两个生产者都被唤醒后就会直接执行 if 语句后的+1,而用 while 的话会重新判断是否需要等待。
JUC版 生产者消费者问题
通过Lock找到Condition
public class B {
public static void main(String[] args) {
Date2 date = new Date2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Date2{
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 {
lock.unlock();
}
}
}
Condition 精准的通知和唤醒线程
public class C {
public static void main(String[] args) {//要求三个线程顺序执行:A->B->C->A
Date3 date3 = new Date3();
new Thread(()->{
for (int i = 0; i < 20; i++) {
date3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
date3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
date3.printC();
}
},"C").start();
}
}
class Date3{
private Lock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
private int number = 1; //1->A 2->B 3->C
public void printA(){
lock.lock();
try {
//业务代码:判断-执行-通知
while (number!=1){
conditionA.await();//A等待
}
System.out.println(Thread.currentThread().getName());
number = 2;
conditionB.signal();//唤醒B
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number!=2){
conditionB.await();
}
System.out.println(Thread.currentThread().getName());
number = 3;
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=3){
conditionC.await();
}
System.out.println(Thread.currentThread().getName());
number = 1;
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、八锁问题
锁到底锁的是谁?
两个东西:new 出来的对象(有多个),Class模版(唯一的)
1、标准状况下,谁先执行完?
发短信
原因:因为加了synchronized
锁,锁的是对象,两个线程执行使用的是同一个对象,所以使用的只有一把锁。发短信先拿到锁,打电话就必须等发短信执行完释放锁。
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
2、发短信业务代码延迟3秒,谁先执行完?
发短信
原因:同上!
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
3、增加了一个普通方法,谁先执行完?
hello
原因:hello方法没有加锁,不受锁的影响,而又因为发短信需要等待3秒,hello方法只需等待1秒,故hello先执行完
public class Test02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
}).start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
4、创建两个对象,两个同步方法,谁先执行完?
打电话
原因:现在有两个对象,即存在两把锁。两个线程使用的对象不同,即使用的锁也不同,执行互不影响。故谁先执行完只看执行需要的时间。打电话->1s 发短信->3s
public class Test02 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.hello();
}).start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
5、两个静态方法,一个对象,谁先执行完?
发短信
原因:一旦静态方法加锁,被锁的是这个对象对应的class
模版(唯一),即只有一把锁,两个线程共用这把锁。
public class Test03 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
new Thread(()->{
phone1.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
}).start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
6、两个静态方法,两个对象,谁先执行完?
发短信
原因:同上!还是因为只有一把锁
public class Test03 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
7、一个静态同步方法,一个普通同步方法,一个对象,谁先执行完?
打电话
原因:静态同步方法锁的是Class模版,普通同步方法锁的是对象。即存在两把锁,两个线程各使用一把,执行完先后顺序由执行所需时间决定。
public class Test04 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
new Thread(()->{
phone1.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
}).start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
8、两个对象,谁先执行完?
打电话
原因:同上!
public class Test04 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);//睡一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);//睡三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
小结
new
出来的对象 --> this
(多个)
static
--> Class
(唯一)
6、集合类不安全
List不安全
//报错:Exception in thread "8" java.util.ConcurrentModificationException
//并发修改异常
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
并发下,List不安全
解决方案:
1、ArrayList<>()
改为Vector<>()
, Vector默认为安全的
List<String> list = new Vector<>();
2、使用Collections
提供的同步方法,把一个List变得安全
List<String> list = Collections.synchronizedList(new ArrayList<>());
3、JUC解法:使用CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
==CopyOnWrite==:写入时复制;COW,计算机程序设计时的一种优化策略
多个线程调用时,读取则都读取固定的。
写入时为了避免覆盖,则先复制一份空间提供写入,写入完毕后再真正放回数组
**思考:**为什么用CopyOnWriteArrayList
而不用Vector
?
因为 Vector 本质上使用的是 synchronized 进行同步,效率较低
CopyOnWriteArrayList 则是使用的Lock锁来完成同步
Set不安全
报错同List
//Exception in thread "16" Exception in thread "24" java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
for (int i = 1; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
解决方案:
1、使用Collections.synchronizedSet(new HashSet<>())
Set<Object> set = Collections.synchronizedSet(new HashSet<>());
2、使用CopyOnWriteArraySet<>()
Set<Object> set = new CopyOnWriteArraySet<>();
思考:
HashSet的底层是什么?
public HashSet() {
map = new HashMap<>();
}
HashSet 的 add 方法又是什么?
//key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map不安全
基础:
map 工作中不用HashMap(存疑?)
默认等价于 new HashMap<>(16,0.75);
Map<String, Object> map = new HashMap<>();
同样的并发修改异常
Exception in thread "19" Exception in thread "10" java.util.ConcurrentModificationException
public class MapTest {
public static void main(String[] args) {
//map 工作中不用HashMap(存疑?)
//默认等价于 new HashMap<>(16,0.75);
Map<String, Object> map = new HashMap<>();
for (int i = 1; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
解决方案:
1、使用Collections.synchronizedMap(new HashMap<>());
Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
2、使用ConcurrentHashMap<>();
Map<String, Object> map = new ConcurrentHashMap<>();
Q.E.D.