1、什么是JUC
- 并发编程目的是:充分利用CPU资源提高程序性能
- java.util.concurrent(java并发编程的工具类)
2、线程
-
进程:例如我们电脑开启的每个应用程序
-
线程:一个进程包含至少一个线程,java中最少有两个线程:main、gc
-
并发:多个线程快速交替操作一个资源类的过程
-
并行:多核CPU情况下,每个CPU可同时执行一个线程,互不抢占CUP资源
-
线程状态
-
NEW:尚未启动的线程的线程状态
-
RUNNABLE:可运行线程的线程状态
-
BLOCKED:等待监视器锁的阻塞线程的线程状态
-
WAITING:等待线程的线程状态
-
TIMED_WAITING:具有指定等待时间的等待线程的线程状态
-
TERMINATED:终止线程的线程状态。线程已经完成执行
-
wait、sleep的区别?
-
类
- wait–>Object的方法
- sleep–>Thread的方法(谁调用谁睡觉,A调用B的sleep方法,则A在睡觉)
-
是否释放锁
- sleep:抱着锁睡觉不释放
- wait:会释放锁
-
使用范围
- wait/notify/notifyAll只能用在同步方法或者同步块中
- sleep可以在任意地方使用
-
异常
- 都需要捕获InterruptedException异常
-
3、synchronized(自动挡)与Lock(手动挡)的区别
- Synchronized是关键字,java内置,Lock是一个java类
- Synchronized无法判断是否获得锁,Lock可以判断
- Synchronized会自动释放锁,Lock需要在finally释放锁(不释放会死锁,多个lock()就要有多个unlock())
- Synchronized一个线程阻塞,其他线程就要永久等下去,Lock可以通过tryLock()尝试获得锁,获取不到就结束等待
- Synchronized可重入(进了大门就能进卧室或者厕所)、不可中断,非公平的,Lock可重入,可中断,可公平非公平(构造支持手动设置公平或者不公平)
- 公平:需要排队,必须先来后到
- 不公平:可插队(好处:提高效率,例如不需要等待执行时间长的线程)
synchronized代码
/**
* 属性 方法 高内聚(不要直接在资源类实现runnable接口)
*/
public class SaleTicket {
private int number =30;
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,还剩"+number+"张票");
}
}
}
/**
* 多线程编程套路:
* 1.高内聚 低耦合 (前提)
* 2.线程 操作(资源类对外暴露的方法) 资源类 (要点)
*
*
* 3个售票员售30张票
*/
class Test{
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
saleTicket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
saleTicket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 30; i++) {
saleTicket.sale();
}
},"C").start();
}
}
Lock 代码
/**
* 属性 方法 高内聚(不要直接在资源类实现runnable接口)
*/
public class SaleTicket2 {
private int number =40;
private Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,还剩"+number+"张票");
}
} finally {
lock.unlock();
}
}
}
/**
* 多线程编程套路:
* 1.高内聚 低耦合 (前提)
* 2.线程 操作(资源类对外暴露的方法) 资源类 (要点)
*
* 3个售票员售40张票
*/
class Test2{
public static void main(String[] args) {
SaleTicket2 saleTicket = new SaleTicket2();
new Thread(()->{
for (int i = 1; i <= 40; i++) {
saleTicket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 40; i++) {
saleTicket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 40; i++) {
saleTicket.sale();
}
},"C").start();
}
}
- 生产者、消费者:判断 干活 通知(线程之间是不能通信,所以需要调度线程)
Synchronized版生产者消费者代码(含虚假唤醒处理)
/**
* 题目: 两个线程,操作一个初始值为0的变量
* 一个线程+1 一个线程-1 判断什么时候+1 什么时候-1
* 交替10次
*
* 多线程编程套路:
* * 1.高内聚 低耦合 (前提)
* * 2.线程 操作(资源类对外暴露的方法) 资源类 (要点)
*
* 生产者消费者模型:判断 干活 通知
*/
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.incrementNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.decrementNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.decrementNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
data.decrementNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int num =0 ;
//+1
public synchronized void incrementNum() throws InterruptedException{
//判断
/*if(num!=0){//会产生虚假唤醒
this.wait();
}*/
while(num!=0){ //while防止虚假唤醒
this.wait();
}
//干活
num++;
System.out.println(Thread.currentThread().getName()+"\t"+num);
//通知
this.notifyAll();
}
//-1
public synchronized void decrementNum() throws InterruptedException{
//判断
/*if(num==0){ 会产生虚假唤醒
this.wait();
}*/
while(num==0){//while防止虚假唤醒
this.wait();
}
//干活
num--;
System.out.println(Thread.currentThread().getName()+"\t"+num);
//通知
this.notifyAll();
}
}
Lock版生产者消费者(含精准唤醒)
/**
* 三个线程 A B C
* 三个线程依次打印
* A 5次
* B 10次
* C 15次
* 依次循环
*
* 精准唤醒 通过num标识
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
data3.print5();
},"A").start();
new Thread(()->{
data3.print10();
},"B").start();
new Thread(()->{
data3.print15();
},"C").start();
}
}
class Data3{
private int num =1 ;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5(){
lock.lock();
try {
//判断
while(num!=1){ //while防止虚假唤醒
condition1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
num=2;
//通知第二个线程干活(指定线程干活)
condition2.signal();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//判断
while(num!=2){ //while防止虚假唤醒
condition2.await();
}
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
num=3;
//通知第三个线程干活(指定线程干活)
condition3.signal();
}catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
//判断
while(num!=3){ //while防止虚假唤醒
condition3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
num=1;
//通知第一个线程干活(指定线程干活)
condition1.signal();
}catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3、8锁现象透彻理解
1、标准访问,请问先打印邮件还是短信?
/**
* 标准访问:先打印邮件还是短信
*
* 邮件
*/
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
//两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
new Thread(()->{
phone.sendEmail();
},"A").start();
new Thread(()->{
phone.sendSm();
},"B").start();
}
}
class Phone{
//synchronized 锁的是方法的调用者
public synchronized void sendEmail(){
System.out.println("sendEmail");
}
public synchronized void sendSm(){
System.out.println("sendSm");
}
}
2、邮件方法暂停4秒钟,请问先打印邮件还是短信?
/**
* **2、邮件方法暂停4秒钟,请问先打印邮件还是短信?**
*
* 邮件
*/
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
//两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
new Thread(()->{
phone.sendEmail();
},"A").start();
new Thread(()->{
phone.sendSm();
},"B").start();
}
}
class Phone2{
//synchronized 锁的是方法的调用者
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sendSm(){
System.out.println("sendSm");
}
}
3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?
/**
* 新增一个普通方法hello()没有同步,请问先打印邮件还是hello?
*
* hello
*/
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
//两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
new Thread(()->{ //先执行
phone.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone.hello();
},"B").start();
}
}
//锁:竞争关系
class Phone3{
//synchronized 锁的是方法的调用者
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sendMS() {
System.out.println("sendMS");
}
//hello没有被Synchronized修饰,不是同步方法,所以不需要等待,其他线程用了一把锁
public void hello(){
System.out.println("hello");
}
}
4、两部手机、请问先打印邮件还是短信?
/**
* 两部手机、请问先打印邮件还是短信?
*
* 短信
*
*/
public class Test4 {
//像:回家 分别进卧室(锁) 卫生间
public static void main(String[] args) throws InterruptedException {
//两个对象,互不干扰
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{ //一开始就执行
phone1.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone2.sendMS();
},"B").start();
}
}
//锁:竞争关系
class Phone4{
//被synchronized修饰的方法,锁的对象是方法的调用者,
// 调用者不同,两者就没有关系,两个方法用的就不是同一个锁
public synchronized void sendEmail(){
//善意的延迟 这个说法真可爱
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sendMS() {
System.out.println("sendMS");
}
}
5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?
/**
* 两个静态同步方法,同一部手机,请问先打印邮件还是短信?
*
* 邮件
*
*/
public class Test5 {
//像:回家 分别进卧室(锁) 卫生间
public static void main(String[] args) throws InterruptedException {
//两个对象,互不干扰
Phone5 phone1 = new Phone5();
new Thread(()->{ //一开始就执行
phone1.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone1.sendMS();
},"B").start();
}
}
class Phone5{
//对象 类模板可以new很多个对象
//class 类模板 只有一个
//被synchronized 和 static 修饰的方法,锁的对象是 类的class对象 唯一的
//同一把锁
public static synchronized void sendEmail(){
//善意的延迟 这个说法真可爱
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void sendMS() {
System.out.println("sendMS");
}
}
6、两个静态同步方法,2部手机,请问先打印邮件还是短信?
/**
* 两个静态同步方法,2部手机,请问先打印邮件还是短信?
*
* 邮件
*
*/
public class Test6 {
//像:回家 分别进卧室(锁) 卫生间
public static void main(String[] args) throws InterruptedException {
//两个对象,同一个class模板,同一把锁 所以先调用的先执行
Phone6 phone1 = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(()->{ //一开始就执行
phone1.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone2.sendMS();
},"B").start();
}
}
class Phone6{
//对象 类模板可以new很多个对象
//class 类模板 只有一个
//被synchronized 和 static 修饰的方法,锁的对象是 类的class对象 唯一的
//同一把锁
public static synchronized void sendEmail(){
//善意的延迟 这个说法真可爱
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void sendMS() {
System.out.println("sendMS");
}
}
7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?
/**
* 一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?
*
* 短信 (对象不一致、class锁跟对象锁互不干预)
*
*/
public class Test7 {
//像:回家 分别进卧室(锁) 卫生间
public static void main(String[] args) throws InterruptedException {
//两个对象,互不干扰
Phone7 phone1 = new Phone7();
new Thread(()->{ //一开始就执行
phone1.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone1.sendMS();
},"B").start();
}
}
class Phone7{
//CLASS
public static synchronized void sendEmail(){
//善意的延迟 这个说法真可爱
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//对象
public synchronized void sendMS() {
System.out.println("sendMS");
}
}
8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?
/**
* 一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?
*
* 短信 (对象不一致、class锁跟对象锁互不干预)
*
*/
public class Test8 {
//像:回家 分别进卧室(锁) 卫生间
public static void main(String[] args) throws InterruptedException {
//两个对象,互不干扰
Phone7 phone1 = new Phone7();
Phone7 phone2 = new Phone7();
new Thread(()->{ //一开始就执行
phone1.sendEmail();
},"A").start();
//干扰
TimeUnit.SECONDS.sleep(1);
new Thread(()->{ //一秒后执行
phone1.sendMS();
},"B").start();
}
}
class Phone8{
//CLASS
public static synchronized void sendEmail(){
//善意的延迟 这个说法真可爱
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("sendEmail");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//对象
public synchronized void sendMS() {
System.out.println("sendMS");
}
}
小结
- new this 本身的这个对象,调用者
- static class 类模板,保证唯一!
- 一个对象中有多个 synchronized 方法,某个时刻内只要有一个线程去访问 synchronized 方法了就会被加锁,独立公共厕所!其他线程就会阻塞!
- 加了一个普通方法后 ,两个对象,无关先后,一个有锁,一个没锁!情况会变化!
- 换成静态同步方法,情况会变化! CLASS ,所有静态同步方法的锁唯一,锁的是对象实例class 本身!
4、集合类不安全
List:单线程安全,多线程不安全
- 单线程
package com.coding.collunsafe;
import java.util.Arrays;
import java.util.List;
public class UnsafeList1 {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println);
}
}
- 多线程
package com.coding.unsafe;
import java.util.*;
/**
* 善于总结:
* 1.故障现象
* 2.导致原因
* 3.解决方法
*
*/
public class UnsafeList2 {
public static void main(String[] args) {
//代码实现
效率高不支持并发(add 方法不加锁)
ArrayList<String> list = new ArrayList<>();
// 测试list多线程下是否安全
//不安全 多线程下会产生一个异常:ConcurrentModificationException
for (int i = 1; i <= 300; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
}).start();
}
}
}
- List多线程不安全解决办法
package com.coding.unsafe;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 善于总结:
* 1.故障现象
* 2.导致原因
* 3.解决方法
*
*/
public class UnsafeList3 {
public static void main(String[] args) {
//代码实现
效率高不支持并发(add 方法不加锁)
//ArrayList<String> list = new ArrayList<>();
// 线程安全的类 效率低(add方法使用锁)
// List<String> list = new Vector<>(); 50
// List<String> list = Collections.synchronizedList(new ArrayList()); 60
//写入时复制 在多线程高并发编程中,一致性最重要
//如果是读 不加锁
//如果是写 就要拷贝一份到自己那里,修改完再放回去
List<String> list = new CopyOnWriteArrayList<>();
// 测试list多线程下是否安全
//不安全 多线程下会产生一个异常:ConcurrentModificationException
for (int i = 1; i <= 300; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
}).start();
}
}
}
Set
package com.coding.unsafe;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class UnsafeSet {
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>();//底层是什么?new HashMap
for (int i = 1; i <= 30; i++) {//多线程会报 ConcurrentModificationException
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
- 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;
public class SafeSet {
public static void main(String[] args) {
//第一种方法 60分
//Set<Object> hashSet = Collections.synchronizedSet(new HashSet<>());
//第二种方法 100分
Set<String> hashSet = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 300; i++) {//多线程会报 ConcurrentModificationException
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
Map
package com.coding.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class UnsafeMap {
public static void main(String[] args) {
Map<String, Object> hashMap = new HashMap<>();
//初始容量 加载因子 上面等同于下面
//Map<String, Object> hashMap = new HashMap<>(16, 0.75F);
for (int i = 1; i <= 300; i++) {//多线程会报 ConcurrentModificationException
new Thread(()->{
hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(hashMap);
},String.valueOf(i)).start();
}
}
}
- Map多线程不安全解决办法
package com.coding.unsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SafeMap {
public static void main(String[] args) {
//第一种方法 coding老师jdk1.8没有
//Map<String, Object> hashMap = Collections.synchronizedMap(new HashMap<>());
//第二种方法
Map<Object, Object> hashMap = new ConcurrentHashMap<>();
for (int i = 1; i <= 300; i++) {
new Thread(()->{
hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(hashMap);
},String.valueOf(i)).start();
}
}
}
5、Callable
- 线程创建方式:Thread、Runnable、Callable区别
- 是否有返回值
- 是否抛出异常(Callable task.get()的时候需要抛出:ExecutionException、InterruptedException)
- 方法不同:run()、call()
package com.coding.callableDemo;
import com.coding.demo02.A;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(Runnable)
//new Thread(RunnableFuture)
//new Thread(FutureTask)
//new Thread(new A(),"A").start();
FutureTask<Integer> task = new FutureTask<>(new B());
new Thread(task,"A").start();//两个线程仍然输出一个结果
new Thread(task,"B").start();//细节1:结果缓存,提高N倍效率
System.out.println(task.get());//获取结果
//细节2:get()一般放在最后,保证结果平稳运行的效率,因为它会阻塞等待结果产生
//用在于线程耗时且结果返回不重要的情况下
}
}
/*class A implements Runnable{
@Override
public void run() {
System.out.println("Runnable");
}
}*/
class B implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Callable");
TimeUnit.SECONDS.sleep(10);
return 1024;
}
}