1、JUC(Java.util.concurrent)
2、线程和进程
一个进程往往包含多个线程,至少包含一个;
java默认有两个线程? main;GC
线程:比进程更小的执行单位,
实现线程的三种方式:Thread,Runnable,Callale
Java开启不了多线程
并发和并行
如果多个线程能同时被多个cpu执行,就是并行。
如果多线程被一个cpu轮流着执行就是并发。
并发编程的本质:充分利用CPU的资源
线程的状态
六种状态:NEW、RUNNABLE、BLOCK、WAITING、TIMEWAITING、TERMINATED
wait、sleep
1、来自不同的类:wait属于Object;sleep属于Thread
2、关于锁的释放:wait释放锁;sleep不释放锁
3、使用的范围是不同的:wait只能在同步代码块或同步控制方法中使用;sleep可以在任意位置使用
4、是否需要捕获异常。
3、LOCK锁
synchronized
/**
- 线程就是一个单独的资源类,没有任何附属操作;
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "A").start();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "B").start();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "C").start();
}
//资源类:属性、方法
class Ticket {
private int number = 1;
public synchronized void saleTicket() {
if (number <= 50) {
System.out.println(Thread.currentThread().getName() + "卖了第" + number + "张票" + "还剩" + (50 - number) + "张票");
number ++;
}
}
}
lock
public class SaleTicketDemo2 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类,把资源类丢入线程
Ticket01 ticket = new Ticket01();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "A").start();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "B").start();
new Thread(() -> {for (int i = 0; i < 20; i++) ticket.saleTicket();}, "C").start();
}
}
//资源类:属性、方法
class Ticket01 {
private int number = 1;
Lock lock = new ReentrantLock();// 创建锁对象
public void saleTicket() {
lock.lock();// 加锁
try {
// 资源操作,业务代码
if (number <= 50) {
System.out.println(
Thread.currentThread().getName() + "卖了第" + number + "张票" + "还剩" + (50 - number) + "张票");
number++;
}
} finally {
lock.unlock();// 释放锁
}
}
}
公平锁:谁先来是谁的
非公平锁:谁抢到是谁的(默认);默认是可重入锁
Synchronized和Lock的区别
- 层面:synchronized属于JVM层面,java关键字;Lock食欲java类
- 是否可以判断锁的状态??
- 锁的释放:Synchronized会自动释放锁,遇到异常会释放锁;Lock需要手动释放,遇到异常容易造成死锁
- 阻塞时的操作:Synchronizd会一直等待;lock.trylock可以选择不等待
- 锁的类型:Synchronized是可重入的非公平锁;lock控制公平不公平
- 适用场景:Synchronized适合少量代码同步问题,Lock适合锁大量的同步代码
4、生产者和消费者问题
面试:单例模式,排序,生产者和消费者,死锁
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.increment();},"A").start();;
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.decrement();},"B").start();
}
}
// 等待,业务,通知
class Ticket{
private int number = 0;
public synchronized void increment(){
if (number != 0) { // 符合条件进入等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number ++;// 业务
System.out.println(Thread.currentThread().getName()+"生产了====>"+number);
this.notifyAll();// 通知其他线程
}
public synchronized void decrement(){
if (number == 0) { // 符合条件进入等待,只会判断一次
try {
this.wait(); // 等待中,当被唤醒时,会直接向下执行,不会在判断number的值,
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number --;// 业务
System.out.println(Thread.currentThread().getName()+"消费了===>" + number);
this.notifyAll(); // 通知其他线程
}
}
多个生产者和消费者问题
防止虚假唤醒问题 判断条件时使用while (CAS)
什么是虚假唤醒:
JUC版的生产者和消费者问题
如何学习:
Lock , await ,signalAll
Synchronized,wait,notifyAll
/*简单实现*/
public class SaleTicket2 {
public static void main(String[] args) {
Ticket02 ticket = new Ticket02();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.increment();},"A").start();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.increment();},"B").start();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.increment();},"C").start();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.increment();},"D").start();
new Thread(()->{for (int i = 0; i <= 2; i++) ticket.decrement();},"E").start();
}
}
// 等待,业务,通知
class Ticket02{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() {
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() {
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();
}
}
}
任何一个新的技术,绝对不是仅仅只是覆盖了原有的技术
实现有序执行:精准的通知和唤醒线程 ABC线程顺序执行
public class SaleTicket2 {
public static void main(String[] args) {
Ticket02 ticket = new Ticket02();
new Thread(()->{for (int i = 1; i <= 3; i++) ticket.printA();},"A").start();
new Thread(()->{for (int i = 1; i <= 4; i++) ticket.printB();},"B").start();
new Thread(()->{for (int i = 1; i <= 5; i++) ticket.printC();},"C").start();
}
}
// 等待,业务,通知
class Ticket02{
private Lock lock = new ReentrantLock();
private int number = 1;// 标识位
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA() {
lock.lock();
try {
while (number!=1) {
conditionA.await(); // 等待
}
number = 2;// 业务
System.out.println(Thread.currentThread().getName());
conditionB.signal();// 通知指定的线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number!=2) {
conditionB.await();//等待
}
number = 3;// 业务
System.out.println(Thread.currentThread().getName());
conditionC.signal(); // 通知指定的线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number!=3) {
conditionC.await();// 等待
}
number = 1;// 业务
System.out.println(Thread.currentThread().getName());
conditionA.signal(); // 通知指定的线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、八锁现象
1、synchronized的锁的对象是方法的调用者:同一个对象会相互干扰,两个方法互不干扰。
2、static静态方法的锁的是class:不管是不是同一对象都会干扰。
3、加锁静态方法和加锁普通方法的锁不一样,互不干扰
4、加锁静态方法和无锁普通方法互不干扰
5、加锁普通方法和无锁普通方法互不干扰
6、集合类不安全
扩展:快速失败、安全失败
ArrayList和HashSet
/*单线程下安全、多线程情况下会发生并发修改异常*/
List<String> asList = Arrays.asList("a","b","c");
asList.forEach(System.out::println)
解决多线程情况的List安全问题
1.解决方法一:
List<String> list = Collections.synchronizedList(new ArrayList<String>());
for (int i = 0; i < 10; i++) {
new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);}).start();
}
2、解决方法二:
// 写入时复制,COW 计算机程序设计领域的一种优化策略;在写入的时候避免覆盖,造成数据问题;读写分离
// 使用的锁是lock,Vector是synchronized
List<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10; i++) {
new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);}).start();
}
HashMap
问题:初始容量16(1<<4),加载因子0.75f
Map<String,String> map = new HashMap<String,String>();// 相当于 new HashMap<String, String>(16,0.75)
?官方文档查看ConcurrentHashMap
1、解决方法一:
Map<Object, String> map = Collections.synchronizedMap(new HashMap<Object, String>());
for (int i = 0; i < 100; i++) {
new Thread(()->{map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);}).start();
}
2、解决方法二:
Map<Object, String> map = new ConcurrentHashMap<Object, String>();
for (int i = 0; i < 100; i++) {
new Thread(()->{map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);}).start();
}