Java入门级教程14——同步安全机制明锁
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;}@Overridepublic 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;}@Overridepublic 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;// 多线程数据安全问题@Overridepublic 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;// 多线程数据安全问题@Overridepublic 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();// 多线程数据安全问题@Overridepublic 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:线程本地变量
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 {// 线程本地变量 key-valueThreadLocal<User> userLocal = new ThreadLocal<User>();private User getUser() {// key:对象hascode() value:对应这个对象User u = userLocal.get(); // 张三key ,李四keyif (null == u) {u = new User();System.out.println(u);userLocal.set(u);}return u;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法");Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);User u = this.getUser();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, "李四");
// Thread s3 = new Thread(u, "王五");s1.start();s2.start();
// s3.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关键字
5.1 保证可见性
可见性指:当一个线程修改了 volatile 修饰的变量后,其他线程能立即看到该变量的最新值,避免因 “线程本地缓存” 导致的旧值读取问题。
5.2 禁止指令重排序
指令重排序是 JVM 为优化性能,对代码执行顺序的调整(不改变单线程语义,但可能破坏多线程语义)。volatile 通过内存屏障(特殊指令)阻止重排序,保证代码执行顺序与预期一致。
5.3 不保证原子性
volatile 仅保证可见性和禁止重排序,但不保证复合操作的原子性(如 i++、i += 1 等包含 “读取 - 修改 - 写入” 的操作)。
5.线程通信
必要条件:同一个对象
5.1 主线程和子线程通信
package com.hy.chapter23;public class UserThread extends Thread {int a;public UserThread(int a) {this.a = a;}public void run() {System.out.println(Thread.currentThread().getName() + ",执行run方法,a的值为:" + a);}}
package com.hy.chapter23;public class Test {public static void main(String[] args) {System.out.println(Thread.currentThread().getName() + ",主线程");int a = 10;UserThread u = new UserThread(a);u.start();}
}
输出结果:
main,主线程
Thread-0,执行run方法,a的值为:10
5.2 子线程和主线程通信
// 1.子线程通过创建callable接口,返回值给主线程。
// 2.通过wait()和notify()方法
// 前提是同一个对象
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) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("主线程获取的值为:" + u.getSum());}}}
没有结果
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)上等待的线程this.notify();// 如果是当前类UserThread的锁,可以默认不写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()(子线程中):唤醒等待锁的线程(此处特指主线程),通知其 “计算已完成,可以获取结果”。
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 blocke.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 blocke.printStackTrace();}}System.out.println("主线程获取的值为:" + ut.getSum());}}
不能输出:System.out.println("主线程获取的值为:" + ut.getSum()); 的结果!
因为:obj.wait()对象 > this.notify()对象
特殊:若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !
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++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.sum += i;// System.out.println(this.sum);}// 算好时,通知主线程获取结果// 若持有的是非线程对象的锁,则解锁需要用该非线程对象,且需要显示解锁 !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();Object obj = new Object();UserThread ut = new UserThread(u);ut.start();// 同步块 ut子线程对象// 主线程持有ut的锁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
// 3.lock(Condition)
单个
package com.hy.chapter3;public class UserThreadA extends Thread {PrintChar p;public UserThreadA(PrintChar p) {this.p = p;}public void run() {this.p.showA();}}
package com.hy.chapter3;public class UserThreadB extends Thread {PrintChar p;public UserThreadB(PrintChar p) {this.p = p;}public void run() {this.p.showB();}}
package com.hy.chapter3;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;public class PrintChar {// 控制标识位private boolean flag = false;private Lock lock;private Condition con;public PrintChar(Lock lock) {this.lock = lock;this.con = lock.newCondition();}public void showA() {try {lock.lock();while (true) {if (this.flag) {try {this.con.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ",A");Thread.sleep(2000);this.flag = true;this.con.signal();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void showB() {try {lock.lock();while (true) {if (!this.flag) {try {this.con.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ",B");Thread.sleep(2000);this.flag = false;this.con.signal();}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}
package com.hy.chapter3;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {public static void main(String[] args) {Lock lock = new ReentrantLock();PrintChar p = new PrintChar(lock);UserThreadA a = new UserThreadA(p);UserThreadB b = new UserThreadB(p);a.start();b.start();}}
多个
package com.hy.chapter4;public class UserThreadA extends Thread {PrintChar p;public UserThreadA(PrintChar p) {this.p = p;}public void run() {this.p.showA();}}
package com.hy.chapter4;public class UserThreadB extends Thread {PrintChar p;public UserThreadB(PrintChar p) {this.p = p;}public void run() {this.p.showB();}}
package com.hy.chapter4;public class UserThreadC extends Thread {PrintChar p;public UserThreadC(PrintChar p) {this.p = p;}public void run() {p.showC();}}
package com.hy.chapter4;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class PrintChar {// 计数器变量private int num = 0;Lock lock = new ReentrantLock();// 创建一个监视器Condition c1 = lock.newCondition();Condition c2 = lock.newCondition();Condition c3 = lock.newCondition();// 5次Apublic void showA() {while (true) {lock.lock();if (num != 0) {try {this.c1.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + ",A");}try {Thread.sleep(2000);num = 1;this.c2.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}// 10次Bpublic void showB() {while (true) {lock.lock();if (num != 1) {try {this.c2.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ",B");}try {Thread.sleep(2000);num = 2;this.c3.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}// 15次Cpublic void showC() {while (true) {lock.lock();if (num != 2) {try {this.c3.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + ",C");}try {Thread.sleep(2000);num = 0;this.c1.signal();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}lock.unlock();}}}
package com.hy.chapter4;/*** 一个线程打印5个A,通知另外一个线程打印10次的B,通知第三个线程打印15次的C * * Lock机制* * @author hy**/public class Test {public static void main(String[] args) {PrintChar p = new PrintChar();UserThreadA a = new UserThreadA(p);UserThreadB b = new UserThreadB(p);UserThreadC c = new UserThreadC(p);a.start();b.start();c.start();}}
5.3 拓展:制作验证码——java spi机制
package com.hy.interfaces;public interface ICode {public String makeCode();}
package com.hy.interfaces.impl;import java.util.Random;import com.hy.interfaces.ICode;public class ChineseCodeImpl implements ICode {private String[] nums = { "赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地" };public String makeCode() {// TODO Auto-generated method stubString code = "";for (int i = 0; i < 4; i++) {String s = nums[new Random().nextInt(nums.length)];if (!code.contains(s)) {code += s;} else {i--;}}return code;}}
package com.hy.interfaces.impl;import java.util.Random;import com.hy.interfaces.ICode;public class NumberCodeImpl implements ICode {private String[] nums = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };public String makeCode() {// TODO Auto-generated method stubString code = "";for (int i = 0; i < 4; i++) {String s = String.valueOf(new Random().nextInt(nums.length));if (!code.contains(s)) {code += s;} else {i--;}}return code;}}
package com.hy.javaspi;import com.hy.interfaces.ICode;
import com.hy.service.CodeServiceFactory;public class App {public static void main(String[] args) {while (true) {String checkCode = CodeServiceFactory.createCode(ICode.class);System.out.println("获取的验证码为:" + checkCode);try {Thread.sleep(6000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}
package com.hy.service;import java.util.Iterator;
import java.util.ServiceLoader;import com.hy.interfaces.ICode;/*** Java面向对象 Java多线程 70% Java反射和注解* * @author hy**/public class CodeServiceFactory {public static String createCode(Class targetClass) {// 服务发现,是通过一个接口的策略文件来动态加载的ServiceLoader s = ServiceLoader.load(targetClass);Iterator its = s.iterator();ICode code = null;while (its.hasNext()) {// 实现子类的动态绑定code = (ICode) its.next();}String checkCode = code.makeCode();return checkCode;}
}
com.hy.interfaces.impl.NumberCodeImpl
#com.hy.interfaces.impl.ChineseCodeImpl