美文网首页
Java 线程安全 与 锁

Java 线程安全 与 锁

作者: Java编程日记 | 来源:发表于2022-04-26 22:25 被阅读0次

Java 线程安全 与 锁

多线程内存模型

  • 线程私有栈内存
    • 每个线程 私有的内存区域
  • 进程公有堆内存
    • 同一个进程 共有的内存区域

为什么会有线程安全问题?

  • 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况

如何解决线程安全问题呢? 加锁

什么是锁?

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

锁可以做什么?

对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。

解锁之后,所有的线程获得竞争此资源的机会。

什么情况下需要加锁?

  • 读读 不需要加锁
  • 写写 需要加锁
  • 读写 需要加锁

加锁的两种方式(synchronized关键字与Lock对象)

第一种:synchronized关键字

  • 方法前加synchronized关键字

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

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

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

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

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

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

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

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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));
        }
    }

}

}</pre>

  • 两个线程类代码

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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();
}

}</pre>

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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();
}

}</pre>

文件Run.java代码如下:

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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();
}

}</pre>

程序运行结果如图所示

[图片上传失败...(image-d1cb95-1650803898663)]

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

[图片上传失败...(image-f1ab98-1650803898663)]

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

  • 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?

    现在创建项目ltl0003进行测试,Number文件代码如下

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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;
}
}</pre>

两个线程类的代码如下:

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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);
    }
    }
    }</pre>

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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());
}
}

}</pre>

主函数文件Run代码如下:

<pre class="prettyprint hljs typescript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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();
    }
    }</pre>

实验结果如图所示

[图片上传失败...(image-f0d7dd-1650803898659)]

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

第二种:Lock对象的使用

  • ReentrantLock类可以达到与synchronized同样的效果。
  • 用法:

<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">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 ();
}</pre>

相关文章

  • 多线程juc锁

    java_basic 1 线程安全 在Java多线程编程当中,实现线程安全: 内部锁(Synchronized...

  • 11.线程安全与锁优化

    线程安全与锁优化 1. 线程安全 按照线程安全的安全程度由强到弱排序,Java中各种操作共享数据分为以下5类:不可...

  • JAVA 多线程与锁

    JAVA 多线程与锁 线程与线程池 线程安全可能出现的场景 共享变量资源多线程间的操作。 依赖时序的操作。 不同数...

  • Java虚拟机总结给面试的你(下)

    本篇博客主要针对Java虚拟机的晚期编译优化,Java内存模型与线程,线程安全与锁优化进行总结,其余部分总结请点击...

  • Java 线程安全 与 锁

    Java 线程安全 与 锁 多线程内存模型 线程私有栈内存每个线程 私有的内存区域 进程公有堆内存同一个进程 共有...

  • iOS的线程安全与锁

    iOS的线程安全与锁

  • Java虚拟机--线程安全和锁优化

    Java虚拟机--线程安全和锁优化 线程安全 线程安全:当多线程访问一个对象时,如果不用考虑这些线程在运行时环境下...

  • 2018-05-08

    多线程 初级概念与传参 join deatch 原子变量 互斥锁 与 线程安全 线程安全 多线程访问冲突 冲突...

  • 高效并发

    从JVM的角度看一下Java与线程,内存模型,线程安全以及JVM对于锁的优化 硬件内存模型与JVM内存模型 硬件的...

  • 高并发编程总纲

    个人介绍 Java多线程,各种并发集合,线程安全。 Java锁,性能以及效率。 框架netty。在netty基础上...

网友评论

      本文标题:Java 线程安全 与 锁

      本文链接:https://www.haomeiwen.com/subject/mvcxyrtx.html