进程是资源分配的最小单位,线程是程序执行的最小单位。计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。
进程是资源分配的最小单位,线程是程序执行的最小单位。计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。
同步与异步指的是程序执行的模式。同步执行是指程序按照顺序执行,每个任务都必须等待前一个任务完成后才能执行;而异步执行是指程序不按照顺序执行,每个任务都可以独立执行,不必等待前一个任务完成。
// 创建线程
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(" running ...");
}
};
// 线程启动
thread.start();
// Lambda写法、创建线程
Thread thread = new Thread(() -> System.out.println(" running ..."));
// 线程启动
thread.start();
// 创建线程
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(" new Runnable Running ...");
}
};
// 线程启动
new Thread(runnable).start();
// Lambda写法
new Thread(() -> {
System.out.println("new Runnable Running ...");
}).start();
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
return "hello world";
}
};
// 线程启动
FutureTask<String> task = new FutureTask<>(callable);
new Thread(task).start();
// 阻塞等待子线程task执行完成
System.out.println("future Task result:" + task.get());
小结
new Thread
是线程与任务合并在一起,方法2是把线程和任务分开Runnable
更容易与线程池等高级API配合Runnable
让任务类脱离了Thread
继承体系,更灵活栈与栈帧
Java Virtual Machine Stacks
Java虚拟机栈
栈(Stack):栈是一种数据结构,用于存储方法调用和局部变量等数据。在Java中,每个线程都有自己的栈,称为虚拟机栈。虚拟机栈是线程私有的,用于存储线程执行方法的栈内存。
栈帧(Stack Frame):栈帧是虚拟机栈的基本单位,用于存储方法的执行信息。每当一个方法被调用时,就会创建一个对应的栈帧,并将其压入虚拟机栈中。栈帧包含了方法的局部变量表、操作数栈、返回地址等信息。当方法执行完毕后,对应的栈帧会被出栈销毁。
因为以下一些原因导致cpu不在执行当前的线程,从而执行另一个线程代码:
sleep
,yield
,wait
,join
,park
,synchronized
,lock
等方法当Context Switch
发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态。Java中对应的概念就是程序计数器(Program Counter Register
),它的作用是记住下一条jvm指令的执行地址,是属于线程私有。
Context Switch
会影响性能方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新线程 | start只能让线程进入就绪状态,里面的代码不一定立刻运行(取决于cpu调度)。每个线程的start方法只能调用一次,如果多次调用则会出现IllegalThreadStateException异常。 |
run() | 新线程启动后会调用的方法 | |
join() | 等待线程运行结束 | 底层原理是 wait(),notify() |
join(long n) | 等待线程运行结束,最多等待n毫秒 | |
getId() | 获取线程长整形的id | |
getName() | 获取线程名 | |
setName(String) | 设置线程名 | |
getPriority() | 获取线程优先级 | |
setPriority(int) | 修改线程优先级 | 规定线程优先级1~10,较大的优先级能提高该线程被cpu调度的几率 |
getState() | 获取线程的状态 | Java中线程状态有6个enum常量:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。 |
isInterrupted() | 判断是否被打断 | 不会清除打断标记 |
isAlive() | 线程是否存活(还没有运行完毕) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep ,wait ,join 会导致打断的线程抛出InterruptedException ,并清除打断标记;如果打算的正在运行的线程,则会设置打断标记,park的线程被打断时,也会设置打断标记 |
interruptd() | 判断当前线程是否被打断 | static方法,会清除打断标记 |
currentThread() | 获取当前正在执行的线程 | static |
sleep(long n) | 让当前线程休眠n毫秒,休眠时会让出cpu时间片给其他线程 | static |
yield() | 提示线程调度器让出当前线程对cpu的使用 |
sleep为例
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
thread.interrupt();
System.out.println(thread.getState());
System.out.println(thread.isInterrupted());
TIMED_WAITING
false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.demo.thread.Demo01.lambda$main$0(Demo01.java:11)
at java.lang.Thread.run(Thread.java:748)
打断正常运行的线程
Thread thread = new Thread(() -> {
Thread currentThread = Thread.currentThread();
while (!currentThread.isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello world");
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
log.debug("线程打断标记:{}", thread.isInterrupted());
log.debug("线程状态:{}", thread.getState());
hello world
hello world
16:33:41.398 [main] DEBUG com.demo.thread.Demo01 - 线程打断标记:true
16:33:41.403 [main] DEBUG com.demo.thread.Demo01 - 线程状态:TERMINATED
两阶段终止
多线程的两阶段终止(Two-Phase Termination)是一种优雅的线程终止方式,通过两个阶段来安全地停止线程的执行。
代码
Thread t1 = new Thread(() -> {
Thread currentThread = Thread.currentThread();
while (!currentThread.isInterrupted()) {
try {
// TODO 业务代码
Thread.sleep(1000);
log.debug("{} - {}", LocalDateTime.now(), "hello world");
} catch (InterruptedException e) {
// 在sleep或wait,join状态时线程执行interrupt方法,会抛出异常并且清楚打断状态,因此需要再次打断标志
currentThread.interrupt();
e.printStackTrace();
}
}
}, "t1");
t1.start();
Thread.sleep(5000);
t1.interrupt();
log.debug("打断标记状态:" + t1.isInterrupted());
16:46:38.087 [t1] DEBUG com.demo.thread.Demo01 - 2023-05-23T16:46:38.085 - hello world
16:46:39.105 [t1] DEBUG com.demo.thread.Demo01 - 2023-05-23T16:46:39.105 - hello world
16:46:40.109 [t1] DEBUG com.demo.thread.Demo01 - 2023-05-23T16:46:40.109 - hello world
16:46:41.115 [t1] DEBUG com.demo.thread.Demo01 - 2023-05-23T16:46:41.115 - hello world
16:46:42.086 [main] DEBUG com.demo.thread.Demo01 - 打断标记状态:true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.demo.thread.Demo01.lambda$main$0(Demo01.java:18)
at java.lang.Thread.run(Thread.java:748)
为什么不使用stop()方法来停止线程?
在多线程中建议使用两阶段模式来优雅的终止线程,使用stop会立刻停止线程,容易破坏同步代码块,造成线程死锁。
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即时守护线程的代码没有执行完,也会强制结束。
通过setDaemon(true);
设置当前线程是否为守护线程,true守护线程,默认false非守护线程
Thread t1 = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(LocalDateTime.now() + " - hello world");
}
});
t1.setName("t1");
t1.setDaemon(true);
t1.start();
log.debug(LocalDateTime.now() + " - hello world");
// 5s后非守护线程结束,t1守护线程会随之结束运行
Thread.sleep(5000);
09:41:18.693 [main] DEBUG com.thread.demo.ThreadDaemonDemo - 2023-05-24T09:41:18.692 - hello world
09:41:19.695 [t1] DEBUG com.thread.demo.ThreadDaemonDemo - 2023-05-24T09:41:19.695 - hello world
09:41:20.709 [t1] DEBUG com.thread.demo.ThreadDaemonDemo - 2023-05-24T09:41:20.709 - hello world
09:41:21.720 [t1] DEBUG com.thread.demo.ThreadDaemonDemo - 2023-05-24T09:41:21.720 - hello world
09:41:22.729 [t1] DEBUG com.thread.demo.ThreadDaemonDemo - 2023-05-24T09:41:22.729 - hello world
从Java API层面讲,线程的生命周期分为六种:NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
。
start
方法;start
方法之后,在JavaAPI层面RUNNABLE
状态涵盖了运行状态及阻塞状态,可运行状态。(由于BIO导致的线程阻塞,在java中无法区分,仍然认为为可运行);
// NEW
Thread thread01 = new Thread(() -> System.out.println("thread01..."));
// RUNNABLE
Thread thread02 = new Thread(() -> {
while (true) {}
});
thread02.start();
// TIMED_WAITING
Thread thread03 = new Thread(() -> {
synchronized (ThreadDemoState.class) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread03.start();
// TERMINATED
Thread thread04 = new Thread(() -> System.out.println("thread04..."));
thread04.start();
// BLOCKED
Thread thread05 = new Thread(() -> {
synchronized (ThreadDemoState.class) {
System.out.println("thread05 ....");
}
});
thread05.start();
// WAITING
Thread thread06 = new Thread(() -> {
try {
thread03.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread06.start();
Thread.sleep(1000);
System.out.println("--------------------------------------");
System.out.println(" thread01: " + thread01.getState());
System.out.println(" thread02: " + thread02.getState());
System.out.println(" thread03: " + thread03.getState());
System.out.println(" thread04: " + thread04.getState());
System.out.println(" thread05: " + thread05.getState());
System.out.println(" thread06: " + thread06.getState());
System.out.println("--------------------------------------");
thread04...
--------------------------------------
thread01: NEW
thread02: RUNNABLE
thread03: TIMED_WAITING
thread04: TERMINATED
thread05: BLOCKED
thread06: WAITING
--------------------------------------
thread05 ....
问题分析
例如对于i++、i--
而言,实际会产生如下的JVM字节码指令:
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatci i // 将修改后的值存入静态变量i
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatci i // 将修改后的值存入静态变量i
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("count:" + count);
}
临界区 Critical Section
为了避免临界区的竞态条件发生,有多种手段可以达到目的
**synchronized**
,**Lock**
Synchronized【对象锁】,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其他线程再想获取这个对象锁时就会阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程的上下文切换。
synchronized(对象)
{
// 临界区
}
class Test{
public synchronized void test(){
}
}
// 等价于
class Test{
public void test(){
synchronized(this){
}
}
}
class Test{
public synchronized static void test(){
}
}
// 等价于
class Test{
public static void test(){
synchronized(Test.class){
}
}
}
class Room {
private int count = 0;
void increment() {
synchronized (this) {
count++;
}
}
void decrement() {
synchronized (this) {
count--;
}
}
int getCount() {
synchronized (this) {
return count;
}
}
}
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("{}", room.getCount());
}
synchronized(this)锁,结果:1 2或2 1
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
synchronized(this)锁,结果:1s后 1 2或2 1s后 1
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
a和b拥有锁的竞争,c直接输出。结果:3 1s后 1 2, 3 2 1s后1 ,2 3 1s后1
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
不是同一个对象锁,结果:2 1s后1
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
a使用Number.class锁,结果:b为this锁。2 1s后1
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
Number.class类锁,结果:1s后 1 2 或 2 1s后1
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
a是类Number.class锁,结果:b是this锁,2 1s后1
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
a 和 b 都是类锁,结果:1s后1 2或2 1s后1
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
局部变量
public static void test(){
int a = 10;
a ++;
}
每个线程对应自己的栈帧,局部变量都为独立的,因此不会出现线程安全问题。
成员变量
class ThreadUnsafe {
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
// { 临界区, 会产生竞态条件
method2();
method3();
// } 临界区
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadUnsafe test = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35)
at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26)
at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14)
at java.lang.Thread.run(Thread.java:748)
上述list对象为类成员变量,当多个线程操作同一个对象时,那对象的成员变量则会出现线程安全问题。
当将上述的list
对象改为局部变量,则不会出现线程安全问题。 但method1,method2方法的修饰范围需要注意,因为当修饰方法不为private时,则有可能对该类进行继承,并且重写某方法,这时需要考虑线程安全问题。从这种情况下我们可以看出private
和final
提供【安全】的意义所在,请体会开闭原则中的闭。就是避免过于开放,导致程序员通过继承类并重写安全的方法,破坏原有的安全性。
class ThreadUnsafe {
public void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
// { 临界区, 会产生竞态条件
method2(list);
method3(list);
// } 临界区
}
}
private void method2(ArrayList<String> list) {
list.add("1");
}
private void method3(ArrayList<String> list) {
list.remove(0);
}
}
这里的线程安全是指,多个线程调用它们实例的某个方法时,是线程安全的。
String类是被final
关键字修饰,不允许程序员重写,保证了原有设计的安全性。String类又拥有不可变类线程安全性,其内部的状态不可以发生变化,比如替换replace,截取substring后的结果是以一个新的字符串对象的形式进行返回,并非对原有的字符串进行修改替换。
单个方法线程安全,那组合的方法是否线程安全呢?
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("key", "value");
hashtable.get("key");
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
table.put("key", value);
}
这样get和put线程安全类方法的组合使用,则还是会有线程安全问题。
@Slf4j
public class TicketDemo {
public static void main(String[] args) {
// 3000人去窗口买100张票
int people = 3000;
// 窗口
TicketWindow ticketWindow = new TicketWindow(100);
// 买到的数量 // new Vector<>();
List<Integer> sellNumList = Collections.synchronizedList(new ArrayList<>());
// 每个线程对象,用于等待最终执行结果
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < people; i++) {
// 买票的数
int num = RandomUtil.randomInt(1, 10);
Thread thread = new Thread(() -> {
try {
// 模拟买票耗时0.5s-1s
Thread.sleep(RandomUtil.randomInt(500, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
// 去买票
int sellNum = ticketWindow.sell(num);
// 买到数量
sellNumList.add(sellNum);
});
thread.start();
threadList.add(thread);
}
threadList.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int sum = sellNumList.stream().mapToInt(e -> e).sum();
log.info("一共售出:" + sum);
log.info("剩余的票:" + ticketWindow.getTotal());
}
}
// 卖票窗口
class TicketWindow {
private int total;
TicketWindow(int total) {
this.total = total;
}
public int getTotal() {
return total;
}
/**
* 出售
*/
public synchronized int sell(int num) {
if (total < num) {
return 0;
}
total -= num;
return num;
}
}
@Slf4j
public class AccountTransferDemo {
public static void main(String[] args) throws InterruptedException {
// 两个用户
Account A = new Account(1000);
Account B = new Account(1000);
// 操作次数
int actionNum = 1000;
// A向B转1000次
Thread aThread = new Thread(() -> {
for (int i = 0; i < actionNum; i++) {
// 转账金额
int transferMoney = RandomUtil.randomInt(10, 50);
A.transfer(B, transferMoney);
}
});
// B向A转1000次
Thread bThread = new Thread(() -> {
for (int i = 0; i < actionNum; i++) {
// 转账金额
int transferMoney = RandomUtil.randomInt(10, 50);
B.transfer(A, transferMoney);
}
});
aThread.start();
bThread.start();
aThread.join();
bThread.join();
log.info("A:{},B:{},两者余额{}", A.getMoney(), B.getMoney(), (A.getMoney() + B.getMoney()));
}
}
class Account {
public Account(int money) {
this.money = money;
}
public int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 转账 target转给谁 money转多少钱
@SneakyThrows
public void transfer(Account target, int money) {
synchronized (Account.class) {
if (this.money >= money) {
// 每次转账耗时
target.setMoney(target.money + money);
this.money -= money;
}
}
}
}
普通对象
Mark Word结构
原理之Monitor
synchronized
,先会查询synchronized
中所指定的对象obj
是否绑定了Monitor
Monitor
绑定,并且将Owner
设为当前线程Monitor
是否已经有了Owner
Owner
与当前线程绑定EntryList
,线程进入阻塞状态BLOCKED
Monitor
的Owner
将临界区的代码执行完毕后,Owner
便会被清空,此时EntryList
中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的。synchronized
后与monitor
绑定时,会将对象头中的Mark Word
设置为Monitor
指针Monitor
,如果synchronized
中所指定的对象不同,则会绑定不同的Monitor总结
偏向锁,轻量级锁,重量级锁,锁重入,自旋优化。
Jvm中对象的头部 mark word区:
● 偏向锁:Thread ID线程id
● 轻量级锁:Lock Record 锁记录地址
● 重量级锁:Monitor 监视器地址
mark word头部状态值:
● 001 正常
● 101 偏向锁
● 00 轻量级锁
● 10 重量级锁
锁之间变化:
● 锁膨胀:轻量级锁情况下,多个线程对临界区资源锁竞争
● 自旋优化:重量级锁情况下,对于竞争锁的时,自旋重试来获取锁资源,避免线程进入阻塞状态,减少线程状态切换,导致资源开销
● 锁重入:轻量级锁情况下,同一个线程多次对一个锁资源进行竞争,导致null的锁记录存在,偏向锁可解决
偏向锁:
● 偏向撤销:hashCode、多个线程使用该对象、wait/notify方法
● 批量撤销:当偏向撤销达到阈值(40),就将整个类的对象都改为不可偏向
● 批量重偏向:当重偏向达到阈值(20),接下来再给对象加锁时,重新偏向至新的线程
synchronized锁在jdk1.6以后默认采用偏向锁,当调用hashCode,多个线程使用该锁对象,调用wait/notify导致偏向锁的撤销,此时会转化为轻量级锁(Lock Record);
当轻量级锁出现锁的竞争时会出现锁膨胀,从而转化为重量级锁(Monitor),底层优化使用自旋来获取竞争的资源。
图解
用于优化解决Monitor这类的重量级锁
轻量级锁:当一个对象被多个线程锁访问,但访问的时间是错开(不存在竞争),避免每次操作加锁解锁导致资源的浪费,此时就可以使用轻量级锁来优化,可能会造成锁的重入
CAS工作原理
原理分析
Lock Record
中的Object reference
指向锁对象object
并尝试替换Object
中的mark word
,将此mark word
放入Lock Record
中保存重量级锁竞争时,还可以使用自旋来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态;
用于优化轻量级锁重入
轻量级锁在没有竞争的情况下,每次重入(该线程的方法中再次锁住该对象,如右代码)操作仍然需要CAS替换操作,这样是会使得性能降低。所以引入了偏向锁对性能进行了优化,在第一次CAS时,会将线程id写入对象的Mark Word中,此后只要发现此线程id表示是自己,就不需要竞争,就不需要再次发生CAS替换,以后只要不发生竞争,这个对象就归该线程所有。
private Object obj = new Object();
public void method1(){
synchronized(obj){
Sysout.out.println("method1");
method2();
}
}
public void method2(){
synchronized(obj){
Sysout.out.println("method2");
}
}
以下几种情况会使得对象偏向锁失效
当撤销偏向锁的阈值达到40以后,就会将整个类的对象的都设置为不可偏向的
public class Test1 {
final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
// 只有在对象被锁住后才能调用wait方法
synchronized (LOCK) {
LOCK.wait();
}
}
}
不同
sleep
是Thread
类的静态方法,wait
是Object
的方法,Object
是所有类的父类,所以所有类都拥有wait方法sleep
在阻塞的时不会释放锁资源,wait
在阻塞时会释放锁sleep
不需要synchronized
一起使用,而wait
需要和synchronized
一起使用(必须对象获取锁以后才能使用)相同
**TIMED_WAITING**
什么时候适合使用wait
注意事项
synchronized (LOCK) {
while(//不满足条件,一直等待,避免虚假唤醒) {
LOCK.wait();
}
//满足条件后再运行
}
synchronized (LOCK) {
//唤醒所有等待线程
LOCK.notifyAll();
}
join原理实际上就是通过wait等待,循环判断其线程的存活状态
isAlive()
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
// 当前时间
long base = System.currentTimeMillis();
// 耗时
long now = 0;
// 校验耗时时间是否已经达到超时
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 是否存活,如果存活则一直等待
while (isAlive()) {
wait(0);
}
} else {
// 避免当前线程的虚假唤醒
while (isAlive()) {
// 当前已等待时间
long delay = millis - now;
if (delay <= 0) {
// 当延迟等待时间达到超时 则退出
break;
}
// 等待
wait(delay);
// 本次耗时时间
now = System.currentTimeMillis() - base;
}
}
}
定义
即Guarded Suspension
,用在一个线程等待另一个线程的执行结果。
GuardedObject
对象join
和Future
的实现就是采用此模式// 中间对象
class Guarded {
private Object result;
public void setResult(Object result) {
synchronized (this) {
this.result = result;
this.notifyAll();
}
}
public Object getResult() throws InterruptedException {
synchronized (this) {
while (result == null) {
this.wait();
}
}
return result;
}
}
public static void main(String[] args) throws InterruptedException {
String data = "hello world";
Guarded guarded = new Guarded();
new Thread(() -> {
// 业务操作,耗时3s(比如下载网络资源,数据统计)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将结果设置给guarded对象
guarded.setResult(data);
}).start();
log.info(LocalDateTime.now() + "_获取数据..");
// 若还没有结果,则会阻塞于此
Object result = guarded.getResult();
log.info(LocalDateTime.now() + "_结果:" + result);
}
11:21:57.115 [main] INFO com.thread.wait.Demo5 - 2023-05-29T11:21:57.113_获取数据..
11:22:00.114 [main] INFO com.thread.wait.Demo5 - 2023-05-29T11:22:00.114_结果:hello world
控制阻塞等待结果时间
class Guarded2 {
public Object result;
public void setResult(Object result) {
synchronized (this) {
this.result = result;
this.notifyAll();
}
}
public Object getResult(long time) throws InterruptedException {
synchronized (this) {
// 进入时间
long now = System.currentTimeMillis();
// 已操作耗时时间(有可能虚假唤醒,需要记录已经等待耗时时间,避免超出用户设置的最大等待时长)
long processTime = 0;
// 避免虚假唤醒
while (result == null) {
// 还剩下多少等待时间
long waitTime = time - processTime;
if (waitTime < 0) {
break;
}
this.wait(time);
// 本次操作耗时
processTime = System.currentTimeMillis() - now;
}
}
return result;
}
}
public static void main(String[] args) throws InterruptedException {
String data = "hello world";
Guarded2 guarded = new Guarded2();
new Thread(() -> {
// 业务操作,耗时3s(比如下载网络资源,数据统计)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将结果设置给guarded对象
guarded.setResult(data);
}).start();
log.info(LocalDateTime.now() + "_获取数据..");
// 若还没有结果,则会阻塞于此
Object result = guarded.getResult(1000);
log.info(LocalDateTime.now() + "_结果:" + result);
}
11:30:10.586 [main] INFO com.thread.wait.Demo6 - 2023-05-29T11:30:10.585_获取数据..
11:30:11.589 [main] INFO com.thread.wait.Demo6 - 2023-05-29T11:30:11.589_结果:null
上述中保护性暂停是一个线程等待另外一个线程返回结果,是一对一结构,若多对多应该如何处理?
public class ExtDemo01 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(1000);
Set<Integer> emailBox = EmailBox.getEmailBoxLength();
for (Integer index : emailBox) {
new Postman(index, "我是消息" + index).start();
}
}
}
// 居民
@Slf4j
class People extends Thread {
@SneakyThrows
@Override
public void run() {
GuardedObject guardedObject = EmailBox.createGuardedObject();
log.info("居民:" + guardedObject.getId() + "-等待收信");
Object result = guardedObject.getResult(10000);
log.info("居民收到信:" + result);
}
}
// 邮递员
@Slf4j
class Postman extends Thread {
private Integer id; // 送给谁
private String email; // 内容
public Postman(Integer id, String email) {
this.id = id;
this.email = email;
}
@SneakyThrows
@Override
public void run() {
GuardedObject guardedObjectById = EmailBox.getGuardedObjectById(id);
guardedObjectById.setResult(email);
log.debug("邮递员送信成功,信件内容:" + guardedObjectById.getId() + "," + id);
}
}
// 信箱
class EmailBox {
public static Map<Integer, GuardedObject> map = new ConcurrentHashMap<>();
private static int index = 1;
// 生成自增id
private synchronized static int generatorId() {
return index++;
}
public static GuardedObject createGuardedObject() {
GuardedObject guardedObject = new GuardedObject(generatorId());
map.put(guardedObject.getId(), guardedObject);
return guardedObject;
}
public synchronized static Set<Integer> getEmailBoxLength() {
return map.keySet();
}
public static GuardedObject getGuardedObjectById(Integer id) {
return map.remove(id);
}
}
// 中间锁
class GuardedObject {
private Integer id;
private Object result;
public GuardedObject(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
@SneakyThrows
public Object getResult(long time) {
synchronized (this) {
// 当前时间
long now = System.currentTimeMillis();
long processTime = 0;
while (this.result == null) {
long wait = time - processTime;
if (wait < 0) {
break;
}
this.wait();
// 当前操作耗时
processTime = System.currentTimeMillis() - now;
}
}
return result;
}
public void setResult(Object result) {
synchronized (this) {
this.result = result;
this.notifyAll();
}
}
}
13:49:07.025 [Thread-1] INFO com.thread.wait.ext01.People - 居民:2-等待收信
13:49:07.025 [Thread-2] INFO com.thread.wait.ext01.People - 居民:3-等待收信
13:49:07.025 [Thread-0] INFO com.thread.wait.ext01.People - 居民:1-等待收信
13:49:08.030 [Thread-1] INFO com.thread.wait.ext01.People - 居民收到信:我是消息2
13:49:08.030 [Thread-3] DEBUG com.thread.wait.ext01.Postman - 邮递员送信成功,信件内容:1,1
13:49:08.030 [Thread-5] DEBUG com.thread.wait.ext01.Postman - 邮递员送信成功,信件内容:3,3
13:49:08.030 [Thread-0] INFO com.thread.wait.ext01.People - 居民收到信:我是消息1
13:49:08.030 [Thread-2] INFO com.thread.wait.ext01.People - 居民收到信:我是消息3
13:49:08.030 [Thread-4] DEBUG com.thread.wait.ext01.Postman - 邮递员送信成功,信件内容:2,2
@Slf4j
public class ExtDemo02 {
public static void main(String[] args) throws InterruptedException {
// 消息队列 最大容量2个
MessageQueue messageQueue = new MessageQueue(2);
// 开启三个线程生产消息
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
try {
messageQueue.put(new Message(finalI, "内容" + finalI));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(1000);
// 开启一个消费者线程
Thread thread = new Thread(() -> {
try {
// 线程没有结束就一直循环
while (!Thread.currentThread().isInterrupted()) {
Message take = messageQueue.take();
log.info("消费消息:" + take);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
// 5s后生产最后一条消息然后关闭消费者线程
Thread.sleep(5000);
messageQueue.put(new Message(10, "最后一条消息"));
thread.interrupt();
}
}
@Data
class Message {
// 消息id
private Integer id;
// 消息主体
private Object body;
public Message(Integer id, Object body) {
this.id = id;
this.body = body;
}
}
@Slf4j
class MessageQueue {
private final LinkedList<Message> queue;
private final Integer capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
this.queue = new LinkedList<>();
}
// 存数据
public void put(Message message) throws InterruptedException {
synchronized (queue) {
while (queue.size() == capacity) {
log.info("消息队列已满");
queue.wait();
}
queue.add(message);
log.info("生产消息:" + message);
queue.notifyAll();
}
}
// 取数据
public Message take() throws InterruptedException {
synchronized (queue) {
while (queue.size() == 0) {
log.info("暂无数据可读");
queue.wait();
}
}
Message firstMessage = queue.remove();
synchronized (queue) {
queue.notifyAll();
}
return firstMessage;
}
}
13:50:52.191 [Thread-0] INFO com.thread.wait.ext01.MessageQueue - 生产消息:Message(id=0, body=内容0)
13:50:52.192 [Thread-2] INFO com.thread.wait.ext01.MessageQueue - 生产消息:Message(id=2, body=内容2)
13:50:52.192 [Thread-1] INFO com.thread.wait.ext01.MessageQueue - 消息队列已满
13:50:53.185 [Thread-1] INFO com.thread.wait.ext01.MessageQueue - 生产消息:Message(id=1, body=内容1)
13:50:53.185 [Thread-3] INFO com.thread.wait.ext01.ExtDemo02 - 消费消息:Message(id=0, body=内容0)
13:50:53.185 [Thread-3] INFO com.thread.wait.ext01.ExtDemo02 - 消费消息:Message(id=2, body=内容2)
13:50:53.185 [Thread-3] INFO com.thread.wait.ext01.ExtDemo02 - 消费消息:Message(id=1, body=内容1)
13:50:53.185 [Thread-3] INFO com.thread.wait.ext01.MessageQueue - 暂无数据可读
13:50:58.195 [main] INFO com.thread.wait.ext01.MessageQueue - 生产消息:Message(id=10, body=最后一条消息)
13:50:58.195 [Thread-3] INFO com.thread.wait.ext01.ExtDemo02 - 消费消息:Message(id=10, body=最后一条消息)
Process finished with exit code 0
定义
park&unpark是LockSupport类中的方法
LockSupport.park(); // 暂停当前线程
LockSupport.unpark(暂停线程对象); // 恢复某个线程对象
先park再unpark
Thread thread = new Thread(() -> {
log.debug("【1】");
// 休眠3s
sleep(1000);
log.debug("【2】");
// 上锁
LockSupport.park();
log.debug("【3】");
}, "t1");
thread.start();
// 3s后调用unPark
sleep(3000);
LockSupport.unpark(thread);
log.debug("【99】");
14:20:46.624 [t1] DEBUG com.thread.wait.ParkDemo - 【1】
14:20:47.627 [t1] DEBUG com.thread.wait.ParkDemo - 【2】
14:20:49.622 [main] DEBUG com.thread.wait.ParkDemo - 【99】
14:20:49.622 [t1] DEBUG com.thread.wait.ParkDemo - 【3】
先unpark再park
Thread thread = new Thread(() -> {
log.debug("【1】");
// 休眠3s
sleep(3000);
log.debug("【2】");
// 上锁
LockSupport.park();
log.debug("【3】");
}, "t1");
thread.start();
// 3s后调用unPark
sleep(1000);
LockSupport.unpark(thread);
log.debug("【99】");
14:25:19.202 [t1] DEBUG com.thread.wait.ParkDemo - 【1】
14:25:20.214 [main] DEBUG com.thread.wait.ParkDemo - 【99】
14:25:22.207 [t1] DEBUG com.thread.wait.ParkDemo - 【2】
14:25:22.207 [t1] DEBUG com.thread.wait.ParkDemo - 【3】
与wait和notify区别
**wait¬ify**
必须配合**synchronized**
一起使用,而**park&unpark**
不需要park&unpark
是以线程为单位来【暂停】和【唤醒】线程,而notify
只能随机唤醒一个等待的线程,notifyAll
是唤醒所有可等待的线程,就不那么精确park&unpark
可以先unpark
再park
,而wait¬ify
不能先notify
每个线程都有自己的一个parker对象,由三部分组成_counter
,_cond
,_mutex
先park再unpark
先unpark再park
对于并发度比较低的锁,可以将锁的粒度细分,创建多把锁。
定义
@Slf4j
public class Demo01 {
private static final Object lock01 = new Object();
private static final Object lock02 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock01) {
log.info("lock01...");
synchronized (lock02) {
log.info("lock02...");
}
}
}).start();
new Thread(() -> {
synchronized (lock02) {
log.info("lock01。。。");
synchronized (lock01) {
log.info("lock02。。。");
}
}
}).start();
}
}
16:05:21.886 [Thread-0] INFO com.thread.lock.Demo01 - lock01...
16:05:21.886 [Thread-1] INFO com.thread.lock.Demo01 - lock01。。。
.... 一直处于等待中
@Slf4j
public class Demo02 {
static int index = 10;
public static void main(String[] args) {
new Thread(() -> {
while (index > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
index--;
log.info("{}", index);
}
}, "t1").start();
new Thread(() -> {
while (index < 20) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
log.info("{}", index);
}
}, "t2").start();
}
}
命令行
打开控制台
jps -l
查看当前服务进程jstack
进程id**linux**
下可以通过 **top**
先定位到 **CPU 占用高**
的 Java 进程,再利用** ****top -Hp 进程id**
来定位是哪个线程,最后再用 jstack 排查jconsole
jdk提供的jconsle.exe工具,地址:jdk1.8.0_31\bin\jconsole.exe
,或者 windows+r 输入 jconsole
运行以下代码,在一段时间过后会导致线程死锁,程序无休止的等待。
public class KxjEatDemo {
public static void main(String[] args) {
Chopsticks k1 = new Chopsticks("k1");
Chopsticks k2 = new Chopsticks("k2");
Chopsticks k3 = new Chopsticks("k3");
Chopsticks k4 = new Chopsticks("k4");
Chopsticks k5 = new Chopsticks("k5");
new Scientist("小A", k1, k2).start();
new Scientist("小B", k2, k3).start();
new Scientist("小C", k3, k4).start();
new Scientist("小D", k4, k5).start();
new Scientist("小E", k5, k1).start();
}
}
@Slf4j
class Scientist extends Thread {
private final Chopsticks left;
private final Chopsticks right;
private final String name;
public Scientist(String name, Chopsticks left, Chopsticks right) {
this.name = name;
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
eat();
}
}
}
}
@SneakyThrows
public void eat() {
log.info(name + "+" + left.getName() + "+" + right.getName(), "【eat...】");
Thread.sleep(1000);
}
}
// 筷子
class Chopsticks {
private String name;
public Chopsticks(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
ReentrantLock可重入锁是java中提供的一种独占锁,它具备与synchronized关键字类似的功能,但更加的灵活和可扩展
// 锁对象
private ReentrantLock lock = new ReentrantLock();
// 获取锁
lock.lock();
try{
// 临界区
}finally{
// 释放锁
lock.unlock();
}
定义
可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的主人,因此在接下来的操作中有权利再次获取这把锁
如果是不可重入锁,那么第二次获取锁时,自己也会被锁挡住
public class Demo03 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
try {
lock.lock();
log.info("method 01 ...");
method2();
}finally {
lock.unlock();
}
}
public static void method2() {
try {
lock.lock();
log.info("method 02 ...");
method3();
}finally {
lock.unlock();
}
}
public static void method3() {
try {
lock.lock();
log.info("method 03 ...");
}finally {
lock.unlock();
}
}
}
18:29:30.231 [main] INFO com.thread.lock.Demo03 - method 01 ...
18:29:30.232 [main] INFO com.thread.lock.Demo03 - method 02 ...
18:29:30.232 [main] INFO com.thread.lock.Demo03 - method 03 ...
lockInterruptibly()
是ReentrantLock
类中的一个方法,用于尝试获取锁,并在获取锁的过程中响应中断信号.t1.interrupe()
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
// 可中断的获取锁资源
log.info("【t1】等待锁资源");
lock.lockInterruptibly();
} catch (InterruptedException e) {
// 线程终端
e.printStackTrace();
log.info("【t1】等待锁资源过程线程中断");
// 因为没有获取到锁,因此不需要执行下面代码,只直接返回
return;
}
try {
log.info("【t1】获取到锁");
} finally {
// 释放锁资源
lock.unlock();
}
}, "t1");
// 主线程获取到锁
lock.lock();
log.info("【main主线程】获取到锁资源");
t1.start();
try {
Thread.sleep(2000);
log.info("【main主线程】中断t1线程");
t1.interrupt();
} finally {
lock.unlock();
}
}
18:40:37.591 [main] INFO com.thread.lock.Demo04 - 【main主线程】获取到锁资源
18:40:37.593 [t1] INFO com.thread.lock.Demo04 - 【t1】等待锁资源
18:40:39.606 [main] INFO com.thread.lock.Demo04 - 【main主线程】中断t1线程
18:40:39.608 [t1] INFO com.thread.lock.Demo04 - 【t1】等待锁资源过程线程中断
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(Unknown Source)
at com.thread.lock.Demo04.lambda$main$0(Demo04.java:21)
at com.thread.lock.Demo04$$Lambda$1/38997010.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
注:如果不是可中断模式(**lockInterruptibly**
),那么即使使用了interrupt
也不会让等待中断
无参形式、立刻失败
public class Demo05 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("t1 开始获取锁");
if (!lock.tryLock()) {
log.info("t1 没有获取到锁");
return;
}
try {
log.info("获取到了锁");
} finally {
log.info("释放了锁");
lock.unlock();
}
}, "t1");
lock.lock();
thread.start();
try {
log.info("main 获取到了锁");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
18:50:00.867 [t1] INFO com.thread.lock.Demo05 - t1 开始获取锁
18:50:00.867 [main] INFO com.thread.lock.Demo05 - main 获取到了锁
18:50:00.869 [t1] INFO com.thread.lock.Demo05 - t1 没有获取到锁 // 立即失败
有参形式、超时失败
public class Demo05 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("t1 开始获取锁");
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
log.info("t1 没有获取到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.info("获取到了锁");
} finally {
log.info("释放了锁");
lock.unlock();
}
}, "t1");
lock.lock();
thread.start();
try {
log.info("main 获取到了锁");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
18:50:19.487 [t1] INFO com.thread.lock.Demo05 - t1 开始获取锁
18:50:19.487 [main] INFO com.thread.lock.Demo05 - main 获取到了锁
18:50:21.501 [t1] INFO com.thread.lock.Demo05 - t1 没有获取到锁 // 而是等了2秒以后才超时结束