Java入门级教程14——同步安全机制明锁、ThreadLocal线程本地量、volatile关键字、线程通信
目录
1.Java多线程同步安全机制
- 基础同步机制:synchronized(内置锁)
- 显式锁:Lock接口(ReentrantLock为代表)
- 原子操作类:java.util.concurrent.atomic
- 线程封闭(避免共享)
- 不可变对象(天然线程安全)
- volatile关键字(轻量级同步)
- 并发容器(高级封装同步)
2.Java多线程同步安全机制——明锁
2.1 公平机制和非公平机制
2.1.1 公平机制
new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行
线程请求锁时,会进入 “等待队列”,按请求顺序排队;只有队首的线程能获取锁,不允许 “插队”。
package com.hy.chapter16;
// 导入java.util.concurrent(JUC并发包)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Buy implements Runnable {
Lock lock;
private boolean flag = true;
private int sum = 10;
public Buy(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while (flag) {
lock.lock(); // 明锁
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");
if (this.sum <= 1) {
this.flag = false;
}
lock.unlock(); // 一定要手动释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 明锁机制
// new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行
// new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行
Lock lock = new ReentrantLock(true);
Buy buy = new Buy(lock);
new Thread(buy, "张三线程").start();
new Thread(buy, "李四线程").start();
new Thread(buy, "王五线程").start();
}
}
输出结果:
张三线程,买到了票,是第10张票
李四线程,买到了票,是第9张票
王五线程,买到了票,是第8张票
张三线程,买到了票,是第7张票
李四线程,买到了票,是第6张票
王五线程,买到了票,是第5张票
张三线程,买到了票,是第4张票
李四线程,买到了票,是第3张票
王五线程,买到了票,是第2张票
张三线程,买到了票,是第1张票
李四线程,买到了票,是第0张票
2.1.2 不公平机制
new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行
线程请求锁时,会先尝试 “插队”(若此时锁刚好被释放,直接获取锁);插队失败才进入等待队列。
package com.hy.chapter16;
// 导入java.util.concurrent(JUC并发包)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Buy implements Runnable {
Lock lock;
private boolean flag = true;
private int sum = 10;
public Buy(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while (flag) {
lock.lock(); // 明锁
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");
if (this.sum <= 1) {
this.flag = false;
}
lock.unlock(); // 一定要手动释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 明锁机制
// new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行
// new ReentrantLock(false); 是false是非公平锁,非公平的意思就是会有一个线程独占执行
Lock lock = new ReentrantLock(false);
Buy buy = new Buy(lock);
new Thread(buy, "张三线程").start();
new Thread(buy, "李四线程").start();
new Thread(buy, "王五线程").start();
}
}
输出结果:
张三线程,买到了票,是第10张票
张三线程,买到了票,是第9张票
张三线程,买到了票,是第8张票
张三线程,买到了票,是第7张票
张三线程,买到了票,是第6张票
张三线程,买到了票,是第5张票
张三线程,买到了票,是第4张票
张三线程,买到了票,是第3张票
张三线程,买到了票,是第2张票
李四线程,买到了票,是第1张票
王五线程,买到了票,是第0张票
2.2 余额变化
package com.hy.chapter17;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Bank {
public Bank(String bankNumber, double money) {
super();
this.bankNumber = bankNumber;
this.money = money;
}
private String bankNumber;
private double money;
public void operatorMoney(double opmoney, Lock lock) {
System.out.println("欢迎您来到银行办理业务");
lock.lock();
this.money += opmoney;
System.out.println(Thread.currentThread().getName() + ",操作的金额是:" + opmoney + ",剩余的金额为:" + this.money);
lock.unlock();
System.out.println("谢谢您的光临");
}
}
class Operator extends Thread {
Bank bank;
double opmoney;
Lock lock;
String threadName;
public Operator(Bank bank, double opmoney, Lock lock, String threadName) {
super(threadName);
this.bank = bank;
this.opmoney = opmoney;
this.lock = lock;
}
public void run() {
this.bank.operatorMoney(opmoney, lock);
}
}
public class Test {
public static void main(String[] args) {
Bank bank = new Bank("10086", 1000);
Lock lock = new ReentrantLock();
Operator op1 = new Operator(bank, 100, lock, "微信支付");
Operator op2 = new Operator(bank, -300, lock, "支付宝支付");
Operator op3 = new Operator(bank, 400, lock, "京东支付");
Operator op4 = new Operator(bank, -500, lock, "华为支付");
Operator op5 = new Operator(bank, 200, lock, "银行卡支付");
op1.start();
op2.start();
op3.start();
op4.start();
op5.start();
}
}
输出结果:
欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
欢迎您来到银行办理业务
微信支付,操作的金额是:100.0,剩余的金额为:1100.0
谢谢您的光临
银行卡支付,操作的金额是:200.0,剩余的金额为:1300.0
谢谢您的光临
华为支付,操作的金额是:-500.0,剩余的金额为:800.0
谢谢您的光临
支付宝支付,操作的金额是:-300.0,剩余的金额为:500.0
谢谢您的光临
京东支付,操作的金额是:400.0,剩余的金额为:900.0
谢谢您的光临
2.3 设置年龄
2.3.1 未加任何锁
package com.hy.chapter18;
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.hy.chapter18;
import java.util.Random;
public class UserRunnable implements Runnable {
private User u;
// 多线程数据安全问题
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",执行run方法");
u = new User();
Random r = new Random();
int age = r.nextInt(100);
System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);
u.setAge(age);
System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
UserRunnable u = new UserRunnable();
Thread s1 = new Thread(u, "张三");
Thread s2 = new Thread(u, "李四");
s1.start();
s2.start();
}
}
输出结果:
张三,执行run方法
李四,执行run方法
张三,产生的随机数的年龄为:16
张三,before设置年龄的值为:16
李四,产生的随机数的年龄为:33
李四,before设置年龄的值为:33
李四,after设置年龄的值为:33
张三,after设置年龄的值为:33
2.3.2 加暗锁
package com.hy.chapter18;
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.hy.chapter18;
import java.util.Random;
public class UserRunnable implements Runnable {
private User u;
// 多线程数据安全问题
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",执行run方法");
// 同步块
synchronized (this) {
u = new User();
Random r = new Random();
int age = r.nextInt(100);
System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);
u.setAge(age);
System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
UserRunnable u = new UserRunnable();
Thread s1 = new Thread(u, "张三");
Thread s2 = new Thread(u, "李四");
s1.start();
s2.start();
}
}
输出结果:
张三,执行run方法
李四,执行run方法
张三,产生的随机数的年龄为:30
张三,before设置年龄的值为:30
张三,after设置年龄的值为:30
李四,产生的随机数的年龄为:91
李四,before设置年龄的值为:91
李四,after设置年龄的值为:91
2.3.2 加明锁
ReentrantLock必须手动释放锁
package com.hy.chapter18;
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.hy.chapter18;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UserRunnable implements Runnable {
private User u;
private Lock lock = new ReentrantLock();
// 多线程数据安全问题
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",执行run方法");
lock.lock();
u = new User();
Random r = new Random();
int age = r.nextInt(100);
System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);
u.setAge(age);
System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
UserRunnable u = new UserRunnable();
Thread s1 = new Thread(u, "张三");
Thread s2 = new Thread(u, "李四");
s1.start();
s2.start();
}
}
输出结果:
李四,执行run方法
张三,执行run方法
李四,产生的随机数的年龄为:43
李四,before设置年龄的值为:43
李四,after设置年龄的值为:43
张三,产生的随机数的年龄为:92
张三,before设置年龄的值为:92
张三,after设置年龄的值为:92
拓展:
线程同步安全机制:1.synchronized 2.lock,锁的特点:以性能换安全
3.ThreadLocal:线程本地变量
ThreadLocal主要用于解决多线程环境下变量的线程安全问题,为每个线程提供了独立的变量副本,实现了线程间的数据隔离:
- 每个线程操作的都是自己线程内的变量副本
- 线程之间不会互相干扰对方的变量值
package com.hy.chapter19;
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.hy.chapter19;
import java.util.Random;
public class UserRunnable implements Runnable {
// 线程本地变量:为每个线程存储独立的User对象副本
// 底层通过线程内部的ThreadLocalMap实现,key为ThreadLocal实例,value为线程私有变量
ThreadLocal<User> userLocal = new ThreadLocal<User>();
private User getUser() {
// 从ThreadLocal中获取当前线程对应的User对象
// 注意:此处获取的对象是当前线程独有的,与其他线程无关
User u = userLocal.get();
// 如果当前线程尚未创建User对象,则初始化并存储到ThreadLocal中
if (null == u) {
u = new User();
// 打印User对象的哈希值,用于观察不同线程的对象是不同的实例
System.out.println(u);
// 将新创建的User对象存入当前线程的ThreadLocal中
userLocal.set(u);
}
return u;
}
@Override
public void run() {
// 打印当前线程名称,标记线程开始执行
System.out.println(Thread.currentThread().getName() + ",执行run方法");
// 创建随机数生成器,用于生成0-99之间的随机年龄
Random r = new Random();
int age = r.nextInt(100);
// 打印当前线程生成的随机年龄
System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);
// 获取当前线程的User对象(线程私有)
User u = this.getUser();
// 设置年龄到当前线程的User对象中
u.setAge(age);
// 打印设置后的年龄(验证设置成功)
System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());
try {
// 线程休眠2秒,模拟实际业务处理时间
// 目的:测试在这段时间内,其他线程是否会影响当前线程的User对象
Thread.sleep(2000);
// 休眠后再次打印年龄,验证线程隔离性(值应与设置时一致)
System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 创建一个UserRunnable实例(多线程共享此任务实例)
UserRunnable u = new UserRunnable();
// 基于同一个任务实例创建两个线程,分别命名为"张三"和"李四"
Thread s1 = new Thread(u, "张三");
Thread s2 = new Thread(u, "李四");
// 启动两个线程,开始执行run方法
s1.start();
s2.start();
}
}
输出结果:
张三,执行run方法
李四,执行run方法
李四,产生的随机数的年龄为:24
张三,产生的随机数的年龄为:40
com.hy.chapter19.User@25ed5eff
com.hy.chapter19.User@b9098ae
张三,before设置年龄的值为:40
李四,before设置年龄的值为:24
李四,after设置年龄的值为:24
张三,after设置年龄的值为:40
锁的特点:以空间换安全
4.volatile关键字
4.1 特点
4.1.1 保证可见性
可见性指:当一个线程修改了 volatile 修饰的变量后,其他线程能立即看到该变量的最新值,避免因 “线程本地缓存” 导致的旧值读取问题。
4.1.2 禁止指令重排序
指令重排序是 JVM 为优化性能,对代码执行顺序的调整(不改变单线程语义,但可能破坏多线程语义)。volatile 通过内存屏障(特殊指令)阻止重排序,保证代码执行顺序与预期一致。
4.1.3 不保证原子性
volatile 仅保证可见性和禁止重排序,但不保证复合操作的原子性(如 i++、i += 1 等包含 “读取 - 修改 - 写入” 的操作)。
4.2 实现变量自增
volatile不能解决非原子操作的线程安全问题。它仅保证可见性和禁止重排序,但对于num++这类包含多步的操作,仍需通过synchronized、lock等方式保证原子性,才能在多线程环境下得到正确结果。
4.2.1 加暗锁
package com.hy.chapter20;
public class Test {
private int num = 0;
public synchronized void addNum() {
num++;
}
public static void main(String[] args) {
Test t = new Test();
for (int i = 0; i < 10; i++) {
Runnable r = () -> {
for (int j = 0; j < 1000; j++) {
t.addNum();
}
};
new Thread(r).start();
}
while (Thread.activeCount() > 1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("获取的值为:" + t.num);
}
}
输出结果:
获取的值为:10000
4.2.2 加明锁
package com.hy.chapter20;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private int num = 0;
public void addNum(Lock lock) {
lock.lock();
num++;
lock.unlock();
}
public static void main(String[] args) {
Test t = new Test();
Lock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
Runnable r = () -> {
for (int j = 0; j < 1000; j++) {
t.addNum(lock);
}
};
new Thread(r).start();
}
while (Thread.activeCount() > 1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("获取的值为:" + t.num);
}
}
输出结果:
获取的值为:10000
4.2.3 用volatile关键字
volatile 关键字存在局限性,特别是在多线程环境下处理非原子操作时可能出现线程安全问题,不能保证非原子操作的原子性
package com.hy.chapter21;
public class Test {
// volatile 关键字在这些场景中的作用是确保变量的可见性和一致性
private volatile int num = 0;
// 对num进行自增操作的方法
public void addNum() {
num++; // 等价于 num = num + 1(非原子操作)
}
public static void main(String[] args) {
Test t = new Test();
// 创建10个线程,每个线程循环1000次调用addNum()
for (int i = 0; i < 10; i++) {
Runnable r = () -> {
for (int j = 0; j < 1000; j++) {
t.addNum();
}
};
new Thread(r).start();
}
// 等待所有子线程执行完毕
while (Thread.activeCount() > 1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印最终的num值
System.out.println("获取的值为:" + t.num);
}
}
输出结果:
获取的值为:9423
4.2.4 拓展:原子操作和非原子操作
- 原子操作指的是「不可分割的操作」:操作一旦开始,就会一直执行到结束,中间不会被任何其他线程打断。
- 非原子操作是「由多个原子步骤组成的复合操作」:这些步骤之间可能被其他线程插入执行,导致操作 “被拆分”。
4.3 volatile关键字保证多线程变量可见性
4.3.1 不加volatile关键字
去掉flag的volatile修饰,程序可能出现子线程无法停止的死循环
package com.hy.chapter22;
public class UserThread extends Thread {
// 用volatile修饰的标志位,控制线程是否继续运行
private volatile boolean flag = true;
@Override
public void run() {
// 只要flag为true,线程就持续循环;flag为false时退出循环
while (flag) {
int a = 10;
a++;
}
System.out.println(Thread.currentThread().getName() + ",线程结束运行");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
package com.hy.chapter22;
public class Test {
public static void main(String[] args) {
UserThread u = new UserThread();
u.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setFlag(false);
}
}
运行结果:出现子线程无法停止的死循环

4.3.2 加volatile关键字
volatile确保了子线程能立即感知主线程对flag的修改,从而正确退出循环
package com.hy.chapter22;
public class UserThread extends Thread {
// 用volatile修饰的标志位,控制线程是否继续运行
private volatile boolean flag = true;
@Override
public void run() {
// 只要flag为true,线程就持续循环;flag为false时退出循环
while (flag) {
// 循环体内的简单操作(避免JVM优化导致循环"固化")
int a = 10;
a++;
}
// 循环结束后,打印线程结束信息
System.out.println(Thread.currentThread().getName() + ",线程结束运行");
}
// 获取flag当前值
public boolean isFlag() {
return flag;
}
// 修改flag的值(用于控制线程停止)
public void setFlag(boolean flag) {
this.flag = flag;
}
}
package com.hy.chapter22;
public class Test {
public static void main(String[] args) {
// 创建UserThread实例并启动线程
UserThread u = new UserThread();
u.start();
try {
// 主线程休眠5秒,让子线程先运行一段时间
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒后,通过修改flag的值停止子线程
u.setFlag(false);
}
}
输出结果:
Thread-0,线程结束运行
4.3.3 保证可见性机制
- 写可见性:主线程修改volatile变量后,会立即同步到主内存(通过写屏障),不允许 “延迟同步”。
- 读可见性:子线程读取volatile变量时,必须从主内存重新读取(通过读屏障),不允许使用工作内存中的缓存值。
5.线程通信
必要条件:同一个对象
5.1 主线程和子线程通信
① UserThread类(子线程类)
作为子线程载体,通过成员变量a接收主线程传递的数据,并在run方法中使用该数据。
package com.hy.chapter23;
public class UserThread extends Thread {
// 子线程的成员变量a,用于接收主线程传递的数据
int a;
// 构造方法:接收外部传入的参数并赋值给成员变量a
public UserThread(int a) {
this.a = a;
}
// 子线程的核心执行方法
@Override
public void run() {
// 打印当前线程名称(子线程)和接收到的a的值
System.out.println(Thread.currentThread().getName() + ",执行run方法,a的值为:" + a);
}
}
② Test类(主线程类)
作为主线程(main方法所在线程),负责创建数据并传递给子线程,启动子线程执行任务。
package com.hy.chapter23;
public class Test {
public static void main(String[] args) {
// 打印主线程名称(默认是"main")
System.out.println(Thread.currentThread().getName() + ",主线程");
// 主线程中定义变量a,值为10
int a = 10;
// 创建子线程实例,并通过构造方法将a的值传递给子线程
UserThread u = new UserThread(a);
// 启动子线程
u.start();
}
}
输出结果:
main,主线程
Thread-0,执行run方法,a的值为:10
5.2 子线程和主线程通信
方式一:子线程通过创建callable接口,返回值给主线程,这种方式暂不讲解。
方式二:通过wait()和notify()方法,前提是同一个对象:
5.2.1 锁的是不同对象
package com.hy.demo14;
public class UserThread extends Thread {
private int sum = 0;
Object obj = new Object();
public void run() {
// 通过Object类对象可以解锁
synchronized (obj) {
for (int i = 0; i <= 100; i++) {
sum += i;
}
// 通过持有同一个对象this被阻塞的线程解锁
obj.notify();
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.demo14;
class User {
}
public class Test {
public static void main(String[] args) {
User u1 = new User();
UserThread u = new UserThread();
u.start();
// 线程通信的必要条件是:同一个对象
// 如果持有的是不是线程类对象,通知解锁的一定是这个类的同一个对象
synchronized (u1) {
try {
// 主线程持有u对象的锁,内阻塞
u1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程获取的值为:" + u.getSum());
}
}
}
没有结果
5.2.2 锁的是相同对象
wait()对象的范围不能大于notify()对象的范围,否则会发生死锁现象
(1)wait()对象的范围不大于notify()对象的范围
①线程对象的锁,用该线程对象(this)来解锁
package com.hy.chapter24;
public class UserThread extends Thread {
private int sum = 0;
public void run() {
// 同步块:锁对象是当前UserThread实例(this)
synchronized (this) {
for (int i = 0; i <= 100; i++) {
this.sum += i;
}
// 计算完成后,唤醒在当前对象(this)上等待的线程
// 如果是当前类UserThread的锁,可以默认不写this.notify();
this.notify(); // 可省略
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.chapter24;
public class Test {
public static void main(String[] args) {
UserThread ut = new UserThread(); // 创建子线程实例
ut.start(); // 启动子线程
// 同步块:锁对象是子线程实例ut(与子线程的锁对象一致)
synchronized (ut) {
try {
// 主线程释放ut的锁,进入等待状态,直到被唤醒
ut.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(ut.getSum());
}
}
输出结果:
5050
wait() 与 notify() 的作用:
ut.wait()(主线程中):释放锁 + 暂停执行,让子线程有机会获取锁并执行计算(避免主线程提前获取未完成的结果)。
this.notify()(子线程中):唤醒等待锁的线程(此处特指主线程),通知其 “计算已完成,可以获取结果”。
②线程对象的锁,用Object对象来解锁
package com.hy.chapter26;
public class UserThread extends Thread {
private int sum = 0;
Object obj;
public UserThread(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("子线程去计算");
synchronized (obj) {
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.sum += i;
// System.out.println(this.sum);
}
// 如果是当前类UserThread的锁,可以默认不写this.notify();
// obj.notify();
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.chapter26;
public class Test {
public static void main(String[] args) {
Object obj = new Object();
UserThread ut = new UserThread(obj);
ut.start();
// 同步块 ut子线程对象
// 主线程持有ut的锁
synchronized (ut) {
System.out.println(Thread.currentThread().getName() + "before...");
try {
// 主线程持有ut的锁,在此被阻塞
ut.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程获取的值为:" + ut.getSum());
}
}
输出结果:
mainbefore...
子线程去计算
主线程获取的值为:5050
(2)wait()对象的范围大于notify()对象的范围,发生死锁现象
package com.hy.chapter27;
public class UserThread extends Thread {
private int sum = 0;
public void run() {
System.out.println("子线程去计算");
synchronized (this) {
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.sum += i;
System.out.println(this.sum);
}
this.notify();
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.chapter27;
public class Test {
public static void main(String[] args) {
Object obj = new Object();
UserThread ut = new UserThread();
ut.start();
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "before...");
try {
// 主线程持有obj的锁,在此被阻塞
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("主线程获取的值为:" + ut.getSum());
}
}
不能输出:System.out.println("主线程获取的值为:" + ut.getSum()); 的结果!
因为:obj.wait()对象 > this.notify()对象
5.2.3 持有非线程对象的锁
若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !
① 不使用这个非线程对象而使用其父类obj对象,发生死锁
package com.hy.demo14;
public class UserThread extends Thread {
private int sum = 0;
Object obj = new Object();
public void run() {
// 通过Object类对象可以解锁
synchronized (obj) {
for (int i = 0; i <= 100; i++) {
sum += i;
}
// 通过持有同一个对象this被阻塞的线程解锁
obj.notify();
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.demo14;
class User {
}
public class Test {
public static void main(String[] args) {
User u1 = new User();
UserThread u = new UserThread();
u.start();
// 线程通信的必须的条件是:同一个对象
// 如果持有的是不是线程类对象,
// 那么通知解锁的一定是这个类的同一个对象
synchronized (u1) {
try {
// 主线程持有u对象的锁,内阻塞
u1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程获取的值为:" + u.getSum());
}
}
}
结果:死锁
② 使用这个非线程对象,成功得到结果
package com.hy.chapter28;
public class UserThread extends Thread {
private int sum = 0;
User u;
public UserThread(User u) {
this.u = u;
}
public void run() {
System.out.println("子线程去计算");
synchronized (u) {
for (int i = 0; i <= 100; i++) {
this.sum += i;
}
// 若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !
u.notify(); // 必须要显示解锁!
}
}
public int getSum() {
return this.sum;
}
}
package com.hy.chapter28;
class User {
}
public class Test {
public static void main(String[] args) {
User u = new User();
UserThread ut = new UserThread(u);
ut.start();
synchronized (u) {
System.out.println(Thread.currentThread().getName() + ",before...");
try {
// 主线程持有ut的锁,在此被阻塞
u.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程获取的值为:" + ut.getSum());
}
}
输出结果:
main,before...
子线程去计算
主线程获取的值为:5050
5.2.4 总结
- 当两个线程通信,分别持有同一个对象的锁(wait和notify),是解锁的前提的条件
- 如果持有的是当前线程类对象的锁,如果是当前线程类对象的自己this和父类,都可以通知解锁,都可以显示的或隐式的写notify
- 线程通信持有的是非线程对象的锁,通知解锁的是这个非线程对象,必须显示的写上非线程对象.notify
5.2.4 对比
不会发生死锁情况:





会发生死锁的情况:



更多推荐


所有评论(0)