一、匿名类部类与线程优先级
1.1 匿名内部类
没有名称的多线程实现类,并且写入一个类的内部或者方法的内部,叫匿名内部类。
package com.aaa.day02_mutilThread.demo1;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author :caicai
* @date :Created in 2022/7/19 14:02
* @description:匿名内部类
* @description:没有名称的多线程实现类,并且写入一个类的内部或者方法的内部,叫匿名内部类。
* @modified By:
* @version:
*/
public class AnonymousInternalClass {
public static void main(String[] args) {
// 继承Thread内部类写法
new Thread(){
@Override
public void run() {
for (int i = 1; i < 11; i++) {
// 获取当前线程
Thread thread = Thread.currentThread();
// 获取线程名称
System.out.println("线程"+thread.getName()+"打印了"+i);
}
}
}.start();
// 实现Runnable内部类的写法
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 11; i < 21; i++) {
Thread thread = Thread.currentThread();
System.out.println("线程"+thread.getName()+"打印了"+i);
}
}
}).start();
// 实现Callable内部类的写法
new Thread(new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
for (int i = 21; i < 31; i++) {
Thread thread = Thread.currentThread();
System.out.println("线程"+thread.getName()+"打印了"+i);
}
String randomStr = UUID.randomUUID().toString();
return randomStr;
}
})).start();
}
}
结果显示:
1.2 线程优先级
setPriority 设置线程A的优先级 优先级一共有10个级别,分别数字1到10 不给线程指定优先级时,默认优先级为5 优先级数字越大,先执行的概率越大
package com.aaa.day02_mutilThread.demo1;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author :caicai
* @date :Created in 2022/7/19 14:02
* @description:线程优先级
* @description:setPriority 设置线程A的优先级 优先级一共有10个级别,分别数字1到10
* 不给线程指定优先级时,默认优先级为5 优先级数字越大,先执行的概率越大
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
// 定义线程A对象
Thread threadA = new Thread(){
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"打印了AAA");
}
};
// 定义线程B对象
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"打印了BBB");
}
});
// 实现Callable内部类的写法
Thread threadC = new Thread(new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("线程"+Thread.currentThread().getName()+"打印了CCC");
return null;
}
}));
// //setPriority 设置线程A的优先级 优先级一共有10个级别,分别数字1到10
// 不给线程指定优先级时,默认优先级为5 优先级数字越大,先执行的概率越大
threadA.setPriority(Thread.MAX_PRIORITY);
// threadA.setPriority(10);
threadB.setPriority(Thread.MIN_PRIORITY);
// threadB.setPriority(1);
threadC.setPriority(Thread.NORM_PRIORITY);
// threadC.setPriority(5);
threadA.setName("MAX_PRIORITY");
threadA.start();
threadB.setName("MIN_PRIORITY");
threadB.start();
threadC.setName("NORM_PRIORITY");
threadC.start();
}
}
结果打印:
二、Synchronized和Reentrantlock区别
2.1 同步和异步
同步:多线程共享一个变量,当一个线程对该变量进行读或者写时,另一个线程不能对该变量进行写或者读,这就叫线程同步。
异步:当一个线程执行一个方法,方法中业务复杂,发出请求,执行时间需要很长,不去等待该方法执行完成,继续执行其他方法,当上面方法执行完成再给我响应,这就叫异步,异步提高程序执行效率。
2.2 Synchronized的用法
2.2.1 Synchronized的常见用法:
修饰普通方法
修饰静态方法
修饰代码块(参考实例)
实例演示:售票
package com.aaa.day02_mutilThread.demo2;
/**
* @author :caicai
* @date :Created in 2022/7/19 14:35
* @description:线程同步使用
* @description:Synchronized的常见用法:修饰普通方法 修饰静态方法 修饰代码块
* @version:
*/
public class SynchronizedTest implements Runnable {
// 票的总数
private int ticketNum = 20;
/**
* 普通方法
*/
public synchronized void methodA(){
// 代码块
}
/**
* 静态方法
*/
public synchronized void methodB(){
// 代码块
}
@Override
public void run() {
while (ticketNum>0){
// synchronized同步块的使用
// this就是锁对象,要求多线程执行时,多个线程拿到的锁对象必须是一个
synchronized (this){
// 不判空出现负票情况
// System.out.println("售票员"+Thread.currentThread().getName()+"售出一张,总票还剩:"+(--ticketNum)+"张");
// 避免出现负票
if (ticketNum>0){
System.out.println("售票员"+Thread.currentThread().getName()+"售出一张,总票还剩:"+(--ticketNum)+"张");
}
}
}
}
public static void main(String[] args) {
SynchronizedTest test = new SynchronizedTest();
new Thread(test,"张三").start();
new Thread(test,"李四").start();
new Thread(test,"王五").start();
}
}
class Test implements Runnable{
private int ticketNum =20;
@Override
public void run() {
while (ticketNum>0){
synchronized (this){
if (ticketNum>0){
System.out.println("销售员"+Thread.currentThread().getName()+"售出一张,余票为"+(--ticketNum));
}
}
}
}
public static void main(String[] args) {
Test test = new Test();
new Thread(test,"张三").start();
new Thread(test,"李四").start();
new Thread(test,"王五").start();
}
}
结果打印:非判空状态
判空状态
2.2.2 synchronized原理
一旦代码块加上synchronized关键字 ,在代码底层就会加上monitorenter,会给对象锁的计算器+1 ,获取锁,其他没有获取到锁的就在外面等待,然后获取锁的执行代码块业务,等业务执行完毕,就会看到monitorexit ,会给对象锁的计数器-1,释放锁,等待才会获取锁,重复该步骤。
打开Open in Terminal后输入:javap -v SynchronizedTest.class
2.3 ReentrantLock用法
线程同步的,jdk1.5之后提供API层面(只能用于同步块)同步锁,要使用lock和unlock配合try finally使用。
2.3.1 代码演示
package com.aaa.day02_mutilThread.demo2;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :caicai
* @date :Created in 2022/7/19 15:03
* @description:线程同步锁 要使用lock 和 unlock配合 try finally
* @modified By:
* @version:
*/
public class ReentrantLockTest implements Runnable{
// 票的总数
private int ticketNum = 20;
// 实例化一个锁对象
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (ticketNum>0){
// 加锁 线程不管能否执行完毕最后都是要解锁的 所以使用try finally 对执行的代码块进行异常捕获 确保最后解锁
reentrantLock.lock();
try {
// 出现负票
//System.out.println("售票员"+Thread.currentThread().getName()+"售出一张,总票还剩:"+(--ticketNum)+"张");
// 避免出现负票
if (ticketNum>0){
//System.out.println(1/0);
System.out.println("售票员"+Thread.currentThread().getName()+"售出一张,总票还剩:"+(--ticketNum)+"张");
}
} finally {
// 解锁
reentrantLock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLockTest test = new ReentrantLockTest();
new Thread(test,"张三").start();
new Thread(test,"李四").start();
new Thread(test,"王五").start();
}
}
结果展示:
2.3.2 提高其他更高级功能:
1)ReentrantLock,当一个线程长时间拿不到锁时,会放弃等待
2)ReentrantLock 支持公平锁
3)ReentrantLock 支持多个对象加锁
2.4 Synchronized和Reentrantlock区别
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对synchronized 进行了非常多的改进。
主要区别如下:
1)ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
2)ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
3)ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。
4)volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
三、sleep和wait区别
1)sleep是Thread类中的静态方法,wait是Object中的成员方法
2)sleep在不在同步块都可以执行,wait必须在同步块中执行,否则报IllegalMonitorStateException异常 (wait执行时要释放锁,如果不在同步中,无法获取锁,无法获取就无法释放)
3)sleep,wait都在同步块中时区别,sleep不释放锁,必须等业务都执行完成后,再释放锁。wait释放锁,直到有其他线程唤醒才会继续执行,被唤醒时不是立马执行,只是具备拿锁资格,要等正在执行的线程执行完毕,释放锁,wait的线程才会执行
sleep代码演示:
package com.aaa.day02_mutilThread.demo3;
/**
* @author :caicai
* @date :Created in 2022/7/19 15:49
* @description:休息时长
* @modified By:
* @version:
*/
public class SleepTest {
public static void main(String[] args) {
Thread threadA = new Thread(){
@Override
public void run() {
System.out.println(111);
try {
// 让线程A先休息 3s
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(222);
}
};
Thread threadB = new Thread(){
@Override
public void run() {
System.out.println(333);
try {
// 让线程B在休息5s
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(444);
}
};
threadA.start();
threadB.start();
}
}
结果打印:
wait代码演示
package com.aaa.day02_mutilThread.demo3;
/**
* @author :caicai
* @date :Created in 2022/7/19 15:49
* @description:等待唤醒
* @modified By:
* @version:
*/
public class WaitTest {
private static Object object = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(){
@Override
public void run() {
// 加锁
synchronized (object) {
System.out.println(111);
try {
// wait执行时要释放锁,如果不在同步中,无法获取锁,无法获取就无法释放
// wait释放锁,直到有其他线程唤醒才会继续执行,被唤醒时不是立马执行,只是具备拿锁资格,要等正在执行的线程执行完毕,释放锁,wait的线程才会执行
object.wait();
// 在同步块中时区别,sleep不释放锁,必须等业务都执行完成后,再释放锁
//Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.notifyAll();
System.out.println(222);
}
}
};
Thread threadB = new Thread(){
@Override
public void run() {
// 加锁
synchronized (object) {
System.out.println(333);
try {
// 唤醒 wait释放锁 object.notifyAll()唤醒所有;
object.notify();
Thread.sleep(1000);
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(444);
}
}
};
threadA.start();
threadB.start();
}
}
结果展示
package com.aaa.day02_mutilThread.demo3;
/**
* @author :caicai
* @date :Created in 2022/7/19 15:49
* @description:等待唤醒
* @modified By:
* @version:
*/
public class WaitTest {
private static Object object = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(){
@Override
public void run() {
// 加锁
synchronized (object) {
System.out.println(111);
try {
// wait执行时要释放锁,如果不在同步中,无法获取锁,无法获取就无法释放
// wait释放锁,直到有其他线程唤醒才会继续执行,被唤醒时不是立马执行,只是具备拿锁资格,要等正在执行的线程执行完毕,释放锁,wait的线程才会执行
object.wait();
// 在同步块中时区别,sleep不释放锁,必须等业务都执行完成后,再释放锁
//Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
object.notifyAll();
System.out.println(222);
}
}
};
Thread threadB = new Thread(){
@Override
public void run() {
// 加锁
synchronized (object) {
System.out.println(333);
try {
// 唤醒 wait释放锁 object.notifyAll()唤醒所有;
object.notify();
object.wait();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(444);
}
}
};
threadA.start();
threadB.start();
}
}
结果展示:
四、死锁及如何避免死锁
4.1 死锁的现象
线程1 持有 资源1(锁1) ,线程2持有资源2(锁2),线程1想要获取资源2(锁2) 线程2要获取资源1(锁1),像这种互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
4.2 死锁实例演示
父亲 拥有玩具 想要儿子成绩单,儿子 拥有成绩单,想要父亲玩具。都想让对方先给, 这样构成死锁
父亲:
package com.aaa.day02_mutilThread.demo4;
public class Father {
public void say(){
System.out.println("你把成绩单给我,我就给你玩具");
}
public void get(){
System.out.println("获得成绩单");
}
}
儿子:
package com.aaa.day02_mutilThread.demo4;
public class Son {
public void say(){
System.out.println("你把玩具给我,我就给你成绩单");
}
public void get(){
System.out.println("获得玩具");
}
}
死锁的形成:
package com.aaa.day02_mutilThread.demo4;
/**
* @author :caicai
* @date :Created in 2022/7/19 16:15
* @description:死锁案例
* @modified By:
* @version:
*/
public class DeadLock implements Runnable{
// 业务对象
private Father father = new Father();
private Son son = new Son();
private boolean isFather;
// 模拟两把锁
private static Object lock1 = new Object();
private static Object lock2 = new Object();
@Override
public void run() {
// 让父亲和儿子一起执行业务
if (isFather){
synchronized (lock1){
// 父亲说话
father.say();
try {
// 先休眠让儿子有机会拿到lock2
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
// 父亲获取资源
father.get();
}
}
}else {
synchronized (lock2){
// 儿子说话
son.say();
try {
// 先休眠让儿子有机会拿到lock1
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
// 儿子获取资源
son.get();
}
}
}
}
public static void main(String[] args) {
DeadLock deadLock1 = new DeadLock();
deadLock1.isFather = true;
DeadLock deadLock2 = new DeadLock();
deadLock2.isFather = false;
// 启动线程
new Thread(deadLock1,"father").start();
new Thread(deadLock2,"son").start();
}
}
结果打印:
4.2.1 如何避免死锁
1) 禁止一个线程持同时持有多个锁
父亲不要同时对lock1和lock2进行锁定 等等
2)具备相同的加锁顺序
父亲先拿lock1 再拿lock2 儿子也先拿lock1 再拿lock2
3) 设置锁超时
现实业务中,一个线程拿到锁后最好设置锁过期,因为业务异常或者其他原因长时间持久锁时,锁会自动过期,有效避免死锁。
4) 死锁检测
使用一定算法拿锁前,先检测会不会发生死锁,如果会就不去拿该锁(适用相同加锁顺序和锁超时都无法使用的场景)