目录

1.Java多线程同步安全机制

2.Java多线程同步安全机制——明锁

2.1 公平机制和非公平机制

2.1.1 公平机制

2.1.2 不公平机制

2.2 余额变化

2.3 设置年龄

2.3.1 未加任何锁

2.3.2 加暗锁

2.3.2 加明锁

3.ThreadLocal:线程本地变量

4.volatile关键字

4.1 特点

4.1.1 保证可见性

4.1.2 禁止指令重排序

4.1.3 不保证原子性

4.2 实现变量自增

4.2.1 加暗锁

4.2.2 加明锁

4.2.3 用volatile关键字

4.2.4 拓展:原子操作和非原子操作

4.3 volatile关键字保证多线程变量可见性

4.3.1 不加volatile关键字

4.3.2 加volatile关键字

4.3.3 保证可见性机制

5.线程通信

5.1 主线程和子线程通信

5.2 子线程和主线程通信

5.2.1 锁的是不同对象

5.2.2 锁的是相同对象

5.2.3 总结

5.2.4 对比


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 对比

不会发生死锁情况:

会发生死锁的情况:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐