1. JUC概述
1.1 JUC简介
一个处理线程的工具包,java.util.concurrent
1.2 进程与线程
进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础, 资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流 程序执行的最小单位
1.3 线程的状态
根据线程状态枚举类Thread.State
可知:
- NEW 新建
- RUNNABLE 运行
- BLOCKED 阻塞
- WAITING 等待(不见不散)
- TIME_WAITING 超时等待(过时不候)
- TERMINATED 结束
wait/sleep的区别
- sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
- sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁。
- 它们都可以被interrupted方法中断。
1.4 并发与并行
并发,一个CPU,交替执行多个任务
并行,多个CPU,同时执行多个任务
1.5 管程
Monitor 监视器 (锁)
1.6 用户线程与守护线程
用户线程:自定义线程(主线程结束,用户线程还在运行,jvm存活)
守护线程:如垃圾回收(主线程结束,守护线程也结束,jvm结束)
public static void main(String[] args){
Thread aa = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"::"+Thread.currentThread().isDaemon());
while(true){
}
},"aa");
//设置守护线程
aa.setDaemon(true);
aa.start();
}
2. Lock接口
2.1 Synchronized
- 修饰一个代码块 Synchronized(this){}
- 修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法
- 修饰一个静态的方法,其作用范围是整个静态方法,作用对象是这个类的所有对象
- 修饰一个类
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 21:31
* @Description: 实现3个售票员,卖出30张票
*/
class Ticket{
//票数
private int number =30;
//操作方法: 卖票
public synchronized void sale() throws InterruptedException {
if(number>0){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : 卖出 : "+(number--)+" 剩下:"+number);
}
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <40 ; i++) {
try {
ticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <40 ; i++) {
try {
ticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <40 ; i++) {
try {
ticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"CC").start();
}
}
2.2 什么是Lock接口
- lock接口介绍
Lock锁实现提供了比使用同步方法与语句可以获得的更广泛的锁操作。他们允许更灵活的结构。 - Lock实现可重入锁
Lock与Synchronized的区别:
Lock不是Java语言内置的,synchronized是Java语言的关键字
Lock必须用户手动释放锁,synchronized是系统自动释放
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待线程会一直等待下去
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 21:53
* @Description: 实现3个售票员,卖出30张票
*/
class LTicket{
//票数
private int number =30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
//操作方法: 卖票
public void sale() throws InterruptedException {
//上锁
lock.lock();
try {
if(number>0){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : 卖出 : "+(number--)+" 剩下:"+number);
}
}finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket lticket = new LTicket();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
2.3 创建线程的多种方式
- 继承Thread类
- 实现Runnable
- 使用Callable
- 使用线程池
ps: 后续说
3.线程间通信
功能:实现两个线程,一个+1 ,一个-1
虚假唤醒:A线程执行+1, B线程和C线程执行-1
假设现在count =0;
- B,C线程调用时,发现不满足,都进入等待状态;
- A线程调用时,发现满足,count++;
- B,C线程都发现count=1,满足条件,进行-1操作,B先 -1,接着 C再-1,此时出现 count=-1。(wait从哪里等待,就从哪里醒来)
总结:
虚假唤醒,就是由于把所有线程都唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功,对于不应该被唤醒的线程而言,便是虚假唤醒。
如何解决
if->while 即可 如下
3.1. 使用synchronized完成
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 22:11
* @Description: 线程通信
*/
//第一步 创建资源类,定义属性和方法
class Share{
//初始值
private int num= 0;
//+1的方法
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
while(num != 0){
this.wait();
}
//如果num=0就+1操作
num++;
System.out.println(Thread.currentThread().getName()+"::"+num);
//通知
this.notifyAll();
}
//-1的方法
public synchronized void decr() throws InterruptedException {
//第二步 判断 干活 通知
while(num != 1){
this.wait();
}
//如果num=1就-1操作
num--;
System.out.println(Thread.currentThread().getName()+"::"+num);
//通知
this.notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//第三步: 创建多个线程,调用资源类的操作方法
Share share = new Share();
//开启线程AA
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
//开启线程BB
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
//开启线程CC
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
//开启线程DD
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
3.2. 使用Lock完成
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 22:37
* @Description: TODO
*/
//第一步 创建资源类,定义属性和方法
class Share{
//初始值
private int num= 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1的方法
public void incr() throws InterruptedException {
//上锁
lock.lock();
try{
//第二步 判断 干活 通知
while(num != 0){
condition.await();
}
//如果num=0就+1操作
num++;
System.out.println(Thread.currentThread().getName()+"::"+num);
//通知
condition.signalAll();
}finally {
//解锁
lock.unlock();
}
}
//-1的方法
public void decr() throws InterruptedException {
//上锁
lock.lock();
try{
//第二步 判断 干活 通知
while(num != 1){
condition.await();
}
//如果num=1就-1操作
num--;
System.out.println(Thread.currentThread().getName()+"::"+num);
//通知
condition.signalAll();
}finally {
//解锁
lock.unlock();
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
4. 线程间定制化通信
需求:
启动三个线程,要求AA打印5次,BB打印10次,CC打印15次,进行10轮
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 22:56
* @Description: 定制化通信
*/
class ShareResource{
//定义标志位: 1:AA 2:BB 3:CC
private int flag=1;
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 =lock.newCondition();
private Condition c2 =lock.newCondition();
private Condition c3 =lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
lock.lock();
try{
while(flag !=1){
c1.await();
}
for(int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"::"+i+" 轮数: "+loop);
}
//通知
flag =2;
c2.signal();
}finally {
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try{
while(flag !=2){
c2.await();
}
for(int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"::"+i+" 轮数: "+loop);
}
//通知
flag =3;
c3.signal();
}finally {
lock.unlock();
}
}
//打印51次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try{
while(flag !=3){
c3.await();
}
for(int i=1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"::"+i+" 轮数: "+loop);
}
//通知
flag =1;
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
shareResource.print5(i); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
shareResource.print10(i); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
shareResource.print15(i); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
5. 集合的线程安全
5.1 ArrayList 集合线程不安全演示
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 错误演示
*/
public class ThreadDemo3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i=0;i<40;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
//Exception in thread "33" java.util.ConcurrentModificationException 并发修改异常
5.1.1 解决方案-Vector
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 用Vector解决并发修改异常
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//创建ArrayList集合 会造成并发修改异常
//List<String> list = new ArrayList<>();
//vector 解决
List<String> list = new Vector<>();
for(int i=0;i<100;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
5.1.2 解决方案-Collections
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 用Collections解决并发修改异常
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//创建ArrayList集合 会造成并发修改异常
//List<String> list = new ArrayList<>();
//Collections解决
List<String> list = Collections.synchronizedList(new ArrayList<>());
for(int i=0;i<100;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
5.1.3 解决方案-CopyOnWriteArrayList
原理:写时复制
写时复制技术:(并发读,独立写)
要读取的时候,支持在原来的地方读
要写的时候,需要复制一份,然后再写入,最后合并
//CopyOnWriteArrayList--add 源码
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();
}
}
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 用CopyOnWriteArrayList 解决并发修改异常
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//创建ArrayList集合 会造成并发修改异常
//List<String> list = new ArrayList<>();
//CopyOnWriteArrayList 解决
List<String> list = new CopyOnWriteArrayList<>();
for(int i=0;i<100;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
5.2 HashSet 线程不安全演示
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 演示HashSet线程不安全
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//演示HashSet线程不安全
Set<String> set = new HashSet<>();
for(int i=0;i<100;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
5.2.1 CopyOnWriteArraySet解决
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: CopyOnWriteArraySet 解决
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//演示HashSet线程不安全
//Set<String> set = new HashSet<>();
//CopyOnWriteArraySet 解决
Set<String> set = new CopyOnWriteArraySet<>();
for(int i=0;i<100;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
5.3 HashMap 线程不安全演示
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: 演示HashMap线程不安全
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//演示HashMap线程不安全
Map<String,String> map = new HashMap<>();
for(int i=0;i<100;i++){
String key = String.valueOf(i);
new Thread(()->{
map.put(key,UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
5.3.1 ConcurrentHashMap 解决
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 23:18
* @Description: ConcurrentHashMap 解决
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//演示HashMap线程不安全
//Map<String,String> map = new HashMap<>();
//ConcurrentHashMap 解决
Map<String,String> map = new ConcurrentHashMap<>();
for(int i=0;i<100;i++){
String key = String.valueOf(i);
new Thread(()->{
map.put(key,UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
6. 多线程锁
6.1 锁的8种情况
class Phone {
public synchronized void sendSMS() throws Exception {
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendSMS AA
------sendEmail BB
原因:如果不加Thread.sleep(100),就不能确定谁先谁后,非静态同步方法的锁是当前对象this,一个类中的所有非静态方法都是同一个锁this,同一时刻只能有一个线程进入同步方法。BB线程被阻塞。
import java.util.concurrent.TimeUnit;
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendSMS AA
------sendEmail BB
原因:sleep方法没有释放锁,4s后输出------sendSMS 接着输出------sendEmail
import java.util.concurrent.TimeUnit;
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
public void hello() {
System.out.println("hello world");
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
hello world
------sendSMS AA
原因:BB调用hello方法没有竞争锁
import java.util.concurrent.TimeUnit;
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone1.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendEmail BB
------sendSMS AA
原因:非静态同步方法的锁是当前对象this,AA线程的锁对象是phone1,BB线程的锁对象是phone2。不是同一个锁,两个线程之间都锁不住对方。
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone= new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendSMS AA
------sendEmail BB
原因:静态同步方法的锁对象是当前类.Class对象,两个线程都是同一个类的对象(同一个对象)。即同一个类的同一个对象。是同一个锁。
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone1= new Phone();
Phone phone2= new Phone();
new Thread(()->{
try {
phone1.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendSMS AA
------sendEmail BB
原因:静态同步方法的锁对象是当前类.Class对象(Class类对象只有1个),两个线程的锁对象都是同一个类的对象(不同对象)。即同一个类的不同对象,但终归属于同一个类,是同一个锁。
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone= new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendEmail BB
------sendSMS AA
原因:sendEmail方法 静态同步方法锁对象是当前类Class类对象,
sendSMS方法 非静态同步方法锁对象是当前对象this。
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"+"\t"+Thread.currentThread().getName());
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail"+"\t"+Thread.currentThread().getName());
}
}
public class Lock_8 {
public static void main(String[] args) throws Exception{
Phone phone1= new Phone();
Phone phone2= new Phone();
new Thread(()->{
try {
phone1.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
输出结果:
------sendEmail BB
------sendSMS AA
原因:sendEmail方法 静态同步方法锁对象是当前类Class类对象,
sendSMS方法 非静态同步方法锁对象是当前对象this。
6.2 公平锁与非公平锁
ReentrantLock lock = new ReentrantLock(true);
/**
* 1. 无参
* public ReentrantLock() {
* sync = new NonfairSync();
* }
* 默认无参是非公平锁
*
* 2.有参
* public ReentrantLock(boolean fair) {
* sync = fair ? new FairSync() : new NonfairSync();
* }
* true 是公平锁
* false 是非公平锁
*/
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: mei_ming
* @DateTime: 2022/5/28 21:53
* @Description: 实现3个售票员,卖出30张票 公平锁与非公平锁
*/
class LTicket{
//票数
private int number =30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock(true);
//操作方法: 卖票
public void sale() throws InterruptedException {
//上锁
lock.lock();
try {
if(number>0){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+" : 卖出 : "+(number--)+" 剩下:"+number);
}
}finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket lticket = new LTicket();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
try {
lticket.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
6.3 可重入锁
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
- synchronized实现
//可重入锁
public class ThreadDemo2 {
//2. synchronized修饰方法
public synchronized void add(){
add();
}
public static void main(String[] args) {
new ThreadDemo2().add();
//结果:Exception in thread "main" java.lang.StackOverflowError
//说明可重入
//synchronized
// Object o = new Object();
// new Thread(()->{
// //1. synchronized修饰对象
// synchronized (o){
// System.out.println(Thread.currentThread().getName()+"外层");
// synchronized (o){
// System.out.println(Thread.currentThread().getName()+"中层");
// synchronized (o){
// System.out.println(Thread.currentThread().getName()+"内层");
// }
// }
// }
// },"t1").start();
}
}
- Lock实现
正常单线程:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: mei_ming
* @DateTime: 2022/5/29 10:31
* @Description: TODO
*/
//可重入锁
public class ThreadDemo2 {
public static void main(String[] args) {
//Lock 演示
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 外层");
try{
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 内层");
}finally {
//解锁
lock.unlock();
}
}finally {
//解锁
lock.unlock();
}
},"t1").start();
}
}
多个线程时,只上锁不解锁 ,可能导致其它线程一直等待的情况
6.4 死锁
- 什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
import java.util.concurrent.TimeUnit;
public class DeadLock {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+" 持有A,试图获取B ");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+" 获取B ");
}
}
},"A").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+" 持有B,试图获取A ");
synchronized (a){
System.out.println(Thread.currentThread().getName()+" 获取A ");
}
}
},"B").start();
}
}
结果:
A 持有A,试图获取B
B 持有B,试图获取A
-
产生死锁的四个必要条件
(1)互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
(2)请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
(4)环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。 -
解决死锁的基本方法
(1)资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
(2)只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
(3)可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
(4)资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件) -
验证是否有死锁:
(1) jps -l
(2) jstack 线程号
结果:Found 1 deadlock
个人笔记,欢迎纠错