4)公平锁、非公平锁:
a:公平锁: 某些线程饿死,效率高。
b:非公平锁:阳光普照,效率低。
// 默认 false,非公平锁,true:公平锁
ReentrantLock reentrantLock = new ReentrantLock(true);
5)可重入锁:(也叫:递归锁)
a:Synchronized(隐式) 和 Lock(显式) 都是可重入锁,
a:解释:第一道锁进入,里面的锁,可以无障碍进入。
6)同步锁使用 Lock 对象充当。
a:什么是 Lock:
b:创建和使用:
-1.从 JDK 5 之后,java 提供了更强大的 线程同步机制-->通过显示定义同步锁对象,来实现同步。
-2.true 公平锁:进入线程有先后循序,先进来的线程,优先获得共享锁资源。
public class TestLock_01 implements Runnable {
public static final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
//执行代码
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
7)面试题:
a:面试题:synchronized 与 lock 异同
b:面试题:如何解决线程安全问题?有几种方式
-1.Lock -> synchronized 同步代码块 -> synchronized 同步方法。
c:面试题:如何优雅的停止线程:(见上面)
d:==== 什么情况会释放锁:====释放锁
-1.当前线程的同步方法、步步代码块执行结束。
-2.当前同步代码块、同步方法中遇到 break、return。
-3.当前线程在 同步代码块、同步方法中 出现了未处理的 Error 或 Exception,导致异常结束。
-4.当前线程在同步代码块、同步方法中,执行了线程对象的 wait() 方法,当前线程休眠,并释放锁。
e:什么情况不会释放锁:
-1.线程执行 同步代码块、同步方法时,调用了 sleep()、yield() 方法暂停线程的执行。
-2.执行同步代码块时,其他线程调用了该线程的 suspend() 方法,将该线程挂起。
Lock 用法练习题:
package com.example.suanfa.queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author zhangxudong@chunyu.me
* @date 2022/4/16 5:46 下午
*/
public class MyLockTest {
/**
* 1 : A
* 2 : B
* 3 : C
*/
private int number = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void AAPrint1() {
lock.lock();
try {
// 判断是否该自己工作
while (number != 1) {
c1.await();
}
// 干活
for (int i = 0; i < 1; i++) {
System.out.println("AAA-" + i);
}
// 唤醒 c2 线程
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void BBPrint2() {
lock.lock();
try {
// 判断是否该自己工作
while (number != 2) {
c2.await();
}
// 干活
for (int i = 0; i < 2; i++) {
System.out.println("BBB--" + i);
}
// 唤醒 c3 线程
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void CCPrint3() {
lock.lock();
try {
// 判断是否该自己工作
while (number != 3) {
c3.await();
}
// 干活
for (int i = 0; i < 3; i++) {
System.out.println("CCC---" + i);
}
// 唤醒 c1 线程
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyLockTest myLockTest = new MyLockTest();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
myLockTest.AAPrint1();
}, "t1").start();
new Thread(() -> {
myLockTest.BBPrint2();
}, "t2").start();
new Thread(() -> {
myLockTest.CCPrint3();
}, "t3").start();
}
}
}
五:线程的通信
1)线程通信的例子:
使用两个线程,打印 1-100。线程1,线程2,交替打印。
public class Test_01 {
public static void main(String[] args) {
A a = new A();
new Thread(a).start();
new Thread(a).start();
}
}
class A implements Runnable {
private static int arr = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
notifyAll();
System.out.println(Thread.currentThread().getName() + " : " + arr);
arr--;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (arr <= 0) {
return;
}
}
}
}
}
2)涉及的三个方法:
wait(): 一旦执行此方法,当前线程,就进入阻塞状态,并释放同步监视器。
notify(): 一旦执行此方法,就会唤醒被 wait() 的一个线程。如果有多个线程被 wait(),就唤醒优先级高的。
notifyAll():一旦执行此方法,就会唤醒所有被 wait() 的线程。
3)注意:
这三个方法,都必须放在 同步代码块 或 同步方法 中,才能执行。
这三个方法的调用者,必须是,同步代码块 或 同步方法 中的 同步监视器,前面省略的 this.wait()
4)面试题:sleep() 方法 和 wait() 方法的异同?
a:相同: 一旦执行方法,都可以使当前线程进入阻塞状态。
他们都可以被 interrupted 方法中断。
b:不同:1.两个方法声明的位置不同:
sleep() --> Thread 类中声明
wait() --> Object 类中声明
2.调用的范围不同:
sleep() --> 任意线程 的 任意位置 都可以调用
wait() --> 必须使用在 同步方法 或 者同步代码块 中
3.关于是否释放同步监视器问题
如果两个方法都是用在,同步代码块 或者 同步方法中
sleep() --> 不会释放同步锁
wait() --> 释放同步锁
5)线程的定制化通信:(c1.wait(),c2.notiffy() )
六:JDK 5.0 新增创建线程方式
1)实现 Callable 接口:
a:与使用 Runnable 相比,Callable 功能更强大些
-1.相比 run() 方法,可以有返回值
-2.方法可以抛出异常
-3.支持泛型的返回值
-4.需要借助 FutureTask 类,比如获取返回结果
b:使用:
public class test_02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
B b = new B();
FutureTask<String> futureTask = new FutureTask<>(b);
new Thread(futureTask).start();
// 查询是否计算完成。
boolean b = futureTask.isDone();
// 运行结束后,获取 回调函数。(建议放在最后面)
// 如果没有计算完成,就会导致阻塞。
String s = futureTask.get();
System.out.println(s);
}
}
class B implements Callable<String> {
@Override
public String call() throws Exception {
return "张三";
}
}
2)使用线程池
a:背景:经常 创建和销毁、使用量特别大得资源,比如:并发情况下的线程,对性能影响很大。
b:思路:提前创建好多个线程,放入线程池中,使用直接获取,用完了再放回线程池中。可以避免 频繁创建、销毁线程,同时实现重复利用。
c:好处:
1.提高相应速度(减少了 线程创建的时间)
2.降低资源能耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
d:代码实现:
public class test_02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// ThreadPoolExecutor executor1 = new ThreadPoolExecutor();
//1:创建 指定说的 数据库连接池
ExecutorService executor = Executors.newFixedThreadPool(20);
//2.执行 Runnable 接口
executor.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
//3.执行 Callable 接口
FutureTask<String> futureTask = new FutureTask<>(() -> "1234");
executor.submit(futureTask);
String s = futureTask.get();
System.out.println(s);
//关闭 线程池
executor.shutdown();
}
}