Java中如何实现线程安全的锁机制?

2026-05-25 15:472阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1440个文字,预计阅读时间需要6分钟。

Java中如何实现线程安全的锁机制?

Java线程安全与锁多线程内存模型、线程私有权栈内存、每个线程私有的内存区域、进程共有堆内存、同一进程共有内存区域。为什么会有线程安全问题时?多线程同时具有对同一资源访问时,可能会出现数据不一致、竞态条件等问题。

Java 线程安全 与 锁 多线程内存模型
  • 线程私有栈内存
    • 每个线程 私有的内存区域
  • 进程公有堆内存
    • 同一个进程 共有的内存区域
为什么会有线程安全问题?
  • 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况
如何解决线程安全问题呢? 加锁 什么是锁?

锁就是对于操作资源的一种权限

锁可以做什么?

对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。
解锁之后,所有的线程获得竞争此资源的机会。

什么情况下需要加锁?
  • 读读 不需要加锁
  • 写写 需要加锁
  • 读写 需要加锁
加锁的两种方式(synchronized关键字与Lock对象) 第一种:synchronized关键字
  • 方法前加synchronized关键字

    • 功能:线程进入用synchronized声明的方法时就上锁,方法执行完自动解锁,锁的是当前类的对象
    • 调用synchronized声明的方法一定是排队运行的
    • 当A线程 调用object对象的synchronized声明的X方法时
      • B线程可以调用其他非synchronized声明的方法
      • B线程不能调用其他synchronized声明的非X方法
  • synchronized锁重入

    • 锁重入的概念:自己可以重复获得自己的内部锁。即synchronized声明的方法,可以调用本对象的其他synchronized方法。
    • 锁重入支持继承的环境,即子类的synchronized方法也可以调用父类的synchronized方法。
  • synchronized同步代码块

    Java中如何实现线程安全的锁机制?

    • synchronized关键字与synchronized代码块的区别

      • synchronized声明的方法是将当前对象作为锁
      • synchronized代码块是将任意对象作为锁
    • 当两个线程访问同一个对象的synchronized代码块时,只有一个线程可以得到执行,另一个线程只能等待当前线程执行完才能执行。

      • 一半同步,一半异步
        • 不在synchronized代码块中就是异步执行,在synchronized代码块中就是同步执行

下面对“一半同步,一半异步”进行代码验证

  • 创建项目ltl0002 ,文件Task的代码如下:

package ltl0002; public class Task { public void doTask(){ for (int i = 0; i < 100; i++) { System.out.println("no synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1)); } synchronized (this){ for (int i = 0; i < 100; i++) { System.out.println("synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1)); } } } }

  • 两个线程类代码

package ltl0002; public class MyThread1 implements Runnable{ private Task task = new Task(); public MyThread1(Task task){ this.task = task; } @Override public void run() { task.doTask(); } }

package ltl0002; public class MyThread2 implements Runnable{ private Task task = new Task(); public MyThread2(Task task){ this.task = task; } @Override public void run() { task.doTask(); } }

文件Run.java代码如下:

package ltl0002; public class Run { public static void main(String[] args) { Task task = new Task(); MyThread1 myThread1 = new MyThread1(task); MyThread2 myThread2 = new MyThread2(task); Thread tr1 = new Thread(myThread1); Thread tr2 = new Thread(myThread2); tr1.start(); tr2.start(); } }

程序运行结果如图所示

进入synchronized代码块之后,排队运行,运行结果如图所示

在第一张图我们可以看到,线程0 和 1交叉输出,说明是异步进行,而在第二张图可以看出线程0运行完之后,线程1才运行,说明它们是同步运行,验证完毕。

  • 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?
    现在创建项目ltl0003进行测试,Number文件代码如下

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:53 * @purpose 成员变量有int num,以及get set方法 */ public class Number { private int num; private boolean change = false; public int getNum() { return num; } public void setNum(int num) { this.num = num; } public boolean isChangeing(){ return change; } public void setChange(boolean change) { this.change = change; } }

两个线程类的代码如下:

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:36 * @purpose 更改num的值 */ public class MyThread01 implements Runnable{ static int num = 0; Number number; public MyThread01(Number num ){ this.number = num ; } @Override public void run() { synchronized (this){ number.setChange(true); for (int i = 0; i < 10000; i++) { number.setNum(num++); } number.setChange(false); } } }

package ltl0003; import static java.lang.Thread.sleep; /** * @author liTianLu * @Date 2022/4/23 15:35 * @purpose 读取num的值 */ public class MyThread02 implements Runnable{ Number number; public MyThread02(Number num ){ this.number = num ; } @Override public void run() { for (int i = 0; i < 1000 ; i++) { //如果number正在更改,就休眠1ms while(number.isChangeing()){ try { sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"的输出为: num = " + number.getNum()); } } }

主函数文件Run代码如下:

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:15 * @purpose 解决锁问题 线程一对num进行修改,线程二三对num进行读取,此代码要实现:线程一与线程二三同步执行,而线程二三异步执行。 */ public class Run { public static void main(String[] args) { Number number = new Number(); number.setNum(0); MyThread01 myThread01 = new MyThread01(number); MyThread02 myThread02 = new MyThread02(number); Thread tr1 = new Thread(myThread01); Thread tr2 = new Thread(myThread02); Thread tr3 = new Thread(myThread02); tr1.start(); tr2.start(); tr3.start(); } }

实验结果如图所示

我们发现,线程2/3执行的时候,线程1已经执行完毕,且线程2、3异步进行。

第二种:Lock对象的使用
  • ReentrantLock类可以达到与synchronized同样的效果。
  • 用法:

ReentrantLock lock = new ReentrantLock (); lock.lock();//加锁 lock.unlock();//解锁 //使用try catch finally 可以确保finally 中的代码执行,在finally中解锁 try{ while(true){ lock.lock (); //操作代码 } }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock (); }

本文共计1440个文字,预计阅读时间需要6分钟。

Java中如何实现线程安全的锁机制?

Java线程安全与锁多线程内存模型、线程私有权栈内存、每个线程私有的内存区域、进程共有堆内存、同一进程共有内存区域。为什么会有线程安全问题时?多线程同时具有对同一资源访问时,可能会出现数据不一致、竞态条件等问题。

Java 线程安全 与 锁 多线程内存模型
  • 线程私有栈内存
    • 每个线程 私有的内存区域
  • 进程公有堆内存
    • 同一个进程 共有的内存区域
为什么会有线程安全问题?
  • 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况
如何解决线程安全问题呢? 加锁 什么是锁?

锁就是对于操作资源的一种权限

锁可以做什么?

对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。
解锁之后,所有的线程获得竞争此资源的机会。

什么情况下需要加锁?
  • 读读 不需要加锁
  • 写写 需要加锁
  • 读写 需要加锁
加锁的两种方式(synchronized关键字与Lock对象) 第一种:synchronized关键字
  • 方法前加synchronized关键字

    • 功能:线程进入用synchronized声明的方法时就上锁,方法执行完自动解锁,锁的是当前类的对象
    • 调用synchronized声明的方法一定是排队运行的
    • 当A线程 调用object对象的synchronized声明的X方法时
      • B线程可以调用其他非synchronized声明的方法
      • B线程不能调用其他synchronized声明的非X方法
  • synchronized锁重入

    • 锁重入的概念:自己可以重复获得自己的内部锁。即synchronized声明的方法,可以调用本对象的其他synchronized方法。
    • 锁重入支持继承的环境,即子类的synchronized方法也可以调用父类的synchronized方法。
  • synchronized同步代码块

    Java中如何实现线程安全的锁机制?

    • synchronized关键字与synchronized代码块的区别

      • synchronized声明的方法是将当前对象作为锁
      • synchronized代码块是将任意对象作为锁
    • 当两个线程访问同一个对象的synchronized代码块时,只有一个线程可以得到执行,另一个线程只能等待当前线程执行完才能执行。

      • 一半同步,一半异步
        • 不在synchronized代码块中就是异步执行,在synchronized代码块中就是同步执行

下面对“一半同步,一半异步”进行代码验证

  • 创建项目ltl0002 ,文件Task的代码如下:

package ltl0002; public class Task { public void doTask(){ for (int i = 0; i < 100; i++) { System.out.println("no synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1)); } synchronized (this){ for (int i = 0; i < 100; i++) { System.out.println("synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1)); } } } }

  • 两个线程类代码

package ltl0002; public class MyThread1 implements Runnable{ private Task task = new Task(); public MyThread1(Task task){ this.task = task; } @Override public void run() { task.doTask(); } }

package ltl0002; public class MyThread2 implements Runnable{ private Task task = new Task(); public MyThread2(Task task){ this.task = task; } @Override public void run() { task.doTask(); } }

文件Run.java代码如下:

package ltl0002; public class Run { public static void main(String[] args) { Task task = new Task(); MyThread1 myThread1 = new MyThread1(task); MyThread2 myThread2 = new MyThread2(task); Thread tr1 = new Thread(myThread1); Thread tr2 = new Thread(myThread2); tr1.start(); tr2.start(); } }

程序运行结果如图所示

进入synchronized代码块之后,排队运行,运行结果如图所示

在第一张图我们可以看到,线程0 和 1交叉输出,说明是异步进行,而在第二张图可以看出线程0运行完之后,线程1才运行,说明它们是同步运行,验证完毕。

  • 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?
    现在创建项目ltl0003进行测试,Number文件代码如下

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:53 * @purpose 成员变量有int num,以及get set方法 */ public class Number { private int num; private boolean change = false; public int getNum() { return num; } public void setNum(int num) { this.num = num; } public boolean isChangeing(){ return change; } public void setChange(boolean change) { this.change = change; } }

两个线程类的代码如下:

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:36 * @purpose 更改num的值 */ public class MyThread01 implements Runnable{ static int num = 0; Number number; public MyThread01(Number num ){ this.number = num ; } @Override public void run() { synchronized (this){ number.setChange(true); for (int i = 0; i < 10000; i++) { number.setNum(num++); } number.setChange(false); } } }

package ltl0003; import static java.lang.Thread.sleep; /** * @author liTianLu * @Date 2022/4/23 15:35 * @purpose 读取num的值 */ public class MyThread02 implements Runnable{ Number number; public MyThread02(Number num ){ this.number = num ; } @Override public void run() { for (int i = 0; i < 1000 ; i++) { //如果number正在更改,就休眠1ms while(number.isChangeing()){ try { sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"的输出为: num = " + number.getNum()); } } }

主函数文件Run代码如下:

package ltl0003; /** * @author liTianLu * @Date 2022/4/23 15:15 * @purpose 解决锁问题 线程一对num进行修改,线程二三对num进行读取,此代码要实现:线程一与线程二三同步执行,而线程二三异步执行。 */ public class Run { public static void main(String[] args) { Number number = new Number(); number.setNum(0); MyThread01 myThread01 = new MyThread01(number); MyThread02 myThread02 = new MyThread02(number); Thread tr1 = new Thread(myThread01); Thread tr2 = new Thread(myThread02); Thread tr3 = new Thread(myThread02); tr1.start(); tr2.start(); tr3.start(); } }

实验结果如图所示

我们发现,线程2/3执行的时候,线程1已经执行完毕,且线程2、3异步进行。

第二种:Lock对象的使用
  • ReentrantLock类可以达到与synchronized同样的效果。
  • 用法:

ReentrantLock lock = new ReentrantLock (); lock.lock();//加锁 lock.unlock();//解锁 //使用try catch finally 可以确保finally 中的代码执行,在finally中解锁 try{ while(true){ lock.lock (); //操作代码 } }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock (); }