JUC:就是我们Java原生的并发包,和一些常用的工具类!
学完之后,很多知识,但是不知道怎么去用!每学习一个知识点,学完之后,可以替换工作中用到的代码!
什么是进程和线程?
进程:QQ.exe
线程:打字、自动保存…..
一个进程可以包含多个线程,一个进程至少有一个线程! Java程序至少有两个线程: GC、Main
并发、并行
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
所以我们使用多线程或者并发编程的目的:提高效率,让CPU一直工作,达到最高处理性能!
线程有几种状态
线程有 6 种状态,面向源码学习!
public enum State {
// java能够创建线程吗? 不能!
// 新建
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待
WAITING,
// 延时等待
TIMED_WAITING,
// 终止!
TERMINATED;
}
wait/Sleep区别
1、类不同!
wait : Obejct 类 Sleep Thread
在juc编程中,线程休眠怎么实现!Thread.Sleep
// 时间单位
TimeUnit.SECONDS.sleep(3);
2、会不会释放资源!
sleep:抱着锁睡得,不会释放锁!wait 会释放锁!
3、使用的范围是不同的;
wait 和 notify 是一组,一般在线程通信的时候使用!
sleep 就是一个单独的方法,在那里都可以用!
4、关于异常;
sleep 需要捕获异常!
synchronized 传统的方式!
笔记:
代码:
package com.coding.demo01;
// 传统的 Synchronized
// Synchronized 方法 和 Synchronized 块
/*
* 我们的学习是基于企业级的开发进行的;
* 1、架构:高内聚,低耦合
* 2、套路:线程操作资源类,资源类是单独的
*/
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
// 1、新建资源类
Ticket ticket = new Ticket();
// 2、线程操纵资源类
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
public void run() {
for (int i = 1; i <=40; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
// 单独的资源类,属性和方法!
// 这样才能实现复用!
class Ticket{
private int number = 30;
// 同步锁,厕所 =>close=>
// synchronized 这是一个关键字
public synchronized void saleTicket(){
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出第"+(number--)+"票,还剩:"+number);
}
}
}
问题:
Lock 锁
笔记:
代码:
package com.coding.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* JUC之后的操作
* Lock锁 + lambda表达式!
*/
public class Demo02 {
public static void main(String[] args) {
// 1、新建资源类
Ticket2 ticket = new Ticket2();
// 2、线程操作资源类 , 所有的函数式接口都可以用 lambda表达式简化!
// lambda表达式 (参数)->{具体的代码}
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"A").start();
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"B").start();
new Thread(()->{for (int i = 1; i <= 40 ; i++) ticket.saleTicket();},"C").start();
}
}
// 依旧是一个资源类
class Ticket2{
// 使用Lock,它是一个对象
// ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...)
// ReentrantLock 默认是非公平锁!
// 非公平锁: 不公平 (插队,后面的线程可以插队)
// 公平锁: 公平(只能排队,后面的线程无法插队)
private Lock lock = new ReentrantLock();
private int number = 30;
public void saleTicket(){
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 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;
面试手写题:单例模式、排序算法、死锁、生产者消费者
线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:
Synchronized 普通版
package com.coding.demo01;
// Synchronized 版
/*
目的: 有两个线程:A B ,还有一个值初始为0,
实现两个线程交替执行,对该变量 + 1,-1;交替10次
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
// +1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// -1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 资源类
// 线程之间的通信: 判断 执行 通知
class Data{
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (number!=0){ // 判断是否需要等待
this.wait();
}
number++; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number==0){ // 判断是否需要等待
this.wait();
}
number--; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
}
四条线程可以实现交替吗?不能,会产生虚假唤醒问题!
转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
注意if 和 while的区别:
package com.coding.demo01;
// Synchronized 版
/*
目的: 有两个线程:A B ,还有一个值初始为0,
实现两个线程交替执行,对该变量 + 1,-1;交替10次
传统的 wait 和 notify方法不能实现精准唤醒通知!
*/
public class Demo03 {
public static void main(String[] args) {
Data data = new Data();
// +1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
// -1
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
// 资源类
// 线程之间的通信: 判断 执行 通知
class Data{
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
while (number!=0){ // 判断是否需要等待
this.wait();
}
number++; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
// -1
public synchronized void decrement() throws InterruptedException {
while (number==0){ // 判断是否需要等待
this.wait();
}
number--; // 执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 通知
this.notifyAll(); //唤醒所有线程
}
}
新版的写法 JUC 挂钩!
转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
package com.coding.demo01;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
实现线程交替执行!
主要的实现目标:精准的唤醒线程!
三个线程:A B C
三个方法:A p5 B p10 C p15 依次循环
*/
public class Demo04 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.print5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
// 资源类
class Data2{
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 print5() throws InterruptedException {
lock.lock();
try {
// 判断
while (number!=1){
condition1.await();
}
// 执行
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知第二个线程干活!
number = 2;
condition2.signal(); // 唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 一定要解锁
}
}
public void print10() throws InterruptedException {
lock.lock();
try {
// 判断
while (number!=2){
condition2.await();
}
// 执行
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知3干活
number = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() throws InterruptedException {
lock.lock();
try {
// 判断
while (number!=3){
condition3.await();
}
// 执行
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 通知 1 干活
number = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
新的技术出来,一定是可以替换一些旧技术的!
利用8道题,搞定锁这个概念!
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/*
1、标准的访问情况下,先执行 sendEmail 还是 sendSMS
答案:sendEmail
被 synchronized 修饰的方式,锁的对象是方法的调用者,所以说这里两个方法调用的对象是同一个
先调用的先执行!
*/
public class LockDemo01 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
class Phone{
public synchronized void sendEmail(){
System.out.println("sendEmail");
}
public synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/*
2、sendEmail休眠3秒后 ,先执行 sendEmail 还是 sendSMS
答案:sendEmail
被 synchronized 修饰的方式,锁的对象是方法的调用者,所以说这里两个方法调用的对象是同一个
先调用的先执行!
*/
public class LockDemo02 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone = new Phone2();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
class Phone2{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/*
3、增加一个普通方法,请问先打印那个 sendEmail 还是 hello
答案:hello
新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
*/
public class LockDemo03 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone3{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendEmail");
}
// 没有 synchronized 没有 static 就是普通方式
public void hello(){
System.out.println("hello");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/*
4、两个手机,请问先执行sendEmail 还是 sendSMS
答案:sendSMS
被 synchronized 修饰的方式,锁的对象是调用者;我们这里有两个调用者,两个方法在这里是两个锁
*/
public class LockDemo04 {
public static void main(String[] args) throws InterruptedException {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
try {
phone1.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
class Phone4{
public synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
//LockDemo05.Class 模板,只有一个 static
//new LockDemo05(),可以创建多个对象
/*
5、两个静态同步方法,同一个手机请问先执行sendEmail 还是 sendSMS
答案:sendEmail
只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!所以说这里是同一个锁
并不是因为synchronized
*/
public class LockDemo05 {
public static void main(String[] args) throws InterruptedException {
Phone5 phone = new Phone5();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
class Phone5{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
//LockDemo05.Class 模板,只有一个 static
//new LockDemo05(),可以创建多个对象
/*
6、两个静态同步方法,两个手机,请问先执行sendEmail 还是 sendSMS
答案:sendEmail
只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!所以说这里是同一个锁
并不是因为synchronized
*/
public class LockDemo06 {
public static void main(String[] args) throws InterruptedException {
Phone6 phone = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
class Phone6{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
//LockDemo05.Class 模板,只有一个 static
//new LockDemo05(),可以创建多个对象
/*
7、一个普通同步方法,一个静态同步方法,只有一个手机,请问先执行sendEmail 还是 sendSMS
答案:sendSMS
synchronized 锁的是这个调用的对象
static 锁的是这个类的Class模板
这里是两个锁!
*/
public class LockDemo07 {
public static void main(String[] args) throws InterruptedException {
Phone7 phone = new Phone7();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
class Phone7{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
//LockDemo05.Class 模板,只有一个 static
//new LockDemo05(),可以创建多个对象
/*
7、一个普通同步方法,一个静态同步方法,两个手机,请问先执行sendEmail 还是 sendSMS
答案:sendSMS
synchronized 锁的是这个调用的对象
static 锁的是这个类的Class模板
这里是两个锁!
*/
public class LockDemo08 {
public static void main(String[] args) throws InterruptedException {
Phone8 phone = new Phone8();
Phone8 phone2 = new Phone8();
new Thread(()->{
try {
phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//Thread.sleep(200);
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
class Phone8{
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println("sendEmail");
}
public synchronized void sendSMS(){
System.out.println("sendSMS");
}
}
小结
1、new this 调用的这个对象,是一个具体的对象!
2、static class 唯一的一个模板!
在我们编写多线程程序得时候,只需要搞明白这个到底锁的是什么就不会出错了!
synchronized(Demo.class){
}
synchronized(this){
}
只要是并发环境,你的集合类都不安全(List、Map、Set)
list 不安全
package com.coding.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 故障现象:ConcurrentModificationException 并发修改异常
* 导致原因:add方法没有锁!
* 解决方案:
* 1、List list = new Vector<>(); //jdk1.0 就存在的!效率低
* 2、List list = Collections.synchronizedList(new ArrayList<>());
* 3、List list = new CopyOnWriteArrayList<>();
*
* 什么是 CopyOnWrite; 写入是复制 (思想 COW)
* 多个调用者同时要相同的资源;这个有一个指针的概念。
* 读写分离的思想:
*/
public class UnSafeList {
public static void main(String[] args) {
// List list = Arrays.asList("a", "b", "c");
// list.forEach(System.out::println);
// List list = new ArrayList<>();
List list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
COW 的思想:(不要害怕看源码,因为到了最底层都是你能够看懂的代码)
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();
}
}
set 不安全
package com.coding.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
// ConcurrentModificationException
public class UnSafeSet {
public static void main(String[] args) {
// HashSet 底层是什么 就是 HashMap
// add,就是 HashMap 的 key;
Set set = new HashSet<>();
// Set set = Collections.synchronizedSet(new HashSet<>());
// Set set = new CopyOnWriteArraySet();
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
map 不安全
package com.coding.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
//ConcurrentModificationException
public class UnsafeMap {
public static void main(String[] args) {
// new HashMap<>() 工作中是这样用的吗? 不是
// 加载因子0.75f;,容量 16; 这两个值工作中不一定这样用!
// 优化性能!
// HashMap 底层数据结构,链表 + 红黑树
// = = = = = = =
// Map map = new HashMap<>();
Map map = new ConcurrentHashMap<>();
// 人生如程序,不是选择就是循环,时常的自我总结十分重要!
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,3));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}