首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java多线程-线程同步机制详解

Java多线程-线程同步机制详解

作者头像
訾博ZiBo
发布2025-01-06 14:05:34
发布2025-01-06 14:05:34
2810
举报

一、线程安全问题概述

多个线程操作同一个数据的情况下,线程不安全了!

二、线程安全问题的代码实现

多线程类:

代码语言:javascript
复制
package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
        }
    }
}

测试类:

代码语言:javascript
复制
package study.thread;

public class ThreadSafeTest {
    public static void main(String[] args) {
        ThreadSafeImpl threadSafe = new ThreadSafeImpl();
        //多线程干一件事
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
    }
}

运行结果(截取部分):

代码语言:javascript
复制
(发现票卖重了,而且卖了不存在的票,这就有问题了!)
售票员Thread-2正在卖第100张票……
售票员Thread-0正在卖第100张票……
售票员Thread-1正在卖第100张票……
...
售票员Thread-2正在卖第1张票……
售票员Thread-0正在卖第1张票……
售票员Thread-1正在卖第-1张票……

三、线程安全问题产生的原理

四、解决线程安全问题

1、引入线程同步机制的三种方法

①同步代码块;

②同步方法;

③锁机制;

2、同步代码块

格式:
代码语言:javascript
复制
synchronized(锁对象){
    可能出现线程安全问题的代码(从访问到共享数据的代码开始)
}
锁对象的含义:

前面的线程开始执行后回去拿取堆内存中的锁对象,后面的线程开始执行后再去拿锁对象就拿不到了,所以无法继续执行,需要等待前面的线程执行完毕,归还锁对象,后面的线程才能拿到锁对象,继续执行;

注意:

①通过代码块中的锁对象,可以使用任意的对象;

②但必须保证多个线程使用的所对象是同一个;

③所对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行;

代码示例:

加入同步代码块后的多线程类:

代码语言:javascript
复制
package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    private final Object object = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (object){
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

测试类:

代码语言:javascript
复制
package study.thread;

public class ThreadSafeTest {
    public static void main(String[] args) {
        ThreadSafeImpl threadSafe = new ThreadSafeImpl();
        //多线程干一件事
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
    }
}

运行结果(截取部分):

代码语言:javascript
复制
售票员Thread-0正在卖第100张票……
售票员Thread-0正在卖第99张票……
售票员Thread-2正在卖第98张票……
售票员Thread-1正在卖第97张票……
售票员Thread-1正在卖第96张票……
售票员Thread-1正在卖第95张票……

3、同步代码块的原理

4、同步方法

使用步骤:

①把访问了共享数据的代码抽取出来,放到一个方法中;

②在方法上添加synchronized修饰符;

格式
代码语言:javascript
复制
访问修饰符 synchronized 返回值类型 方法名(参数列表){
    //方法体
}
代码示例:
代码语言:javascript
复制
package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        printMessage();
    }
    private synchronized void printMessage(){
        while (true){
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
        }
    }
}

但经测试发现,此方法一个会出现一个线程将票卖完的情况,我自己猜测也许是因为while循环放进了锁住的方法中。

改进后的代码:
代码语言:javascript
复制
package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            printMessage();
            if(ticket==0){
                break;
            }
        }
    }
    private synchronized void printMessage(){
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
            ticket--;
        }
    }
}

这样写确实避免了上述情况。

备注:

同步方法也会把方法内部的代码锁住,只让一个线程执行,实际上所的对象是new RunnableImpl(),也就是this(自身);

5、静态同步方法

概述:

静态同步方法就是在一般的同步方法synchronized前加上static;

注意:

此时的锁对象不是this(本身),而是本类的class属性-->class文件对象(反射);

6、锁机制(Lock锁)

概述:

Lock接口实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作;

使用步骤:

①在成员位置创建一个ReentrantLock(可重入锁);

②在有可能出现安全问题的代码前,调用获取锁的方法(闭锁);

③在有可能出现安全问题的代码后,调用释放锁的方法(开锁);

代码示例:
代码语言:javascript
复制
package study.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    //1、在成员位置创建一个
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
            //3、在有可能出现安全问题的代码后,调用释放锁的方法(开锁)
            lock.unlock();
        }
    }
}
代码示例更好的写法:
代码语言:javascript
复制
package study.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    //1、在成员位置创建一个
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3、在有可能出现安全问题的代码后,调用释放锁的方法(开锁)
                    lock.unlock();
                }
            }else {
                break;
            }
        }
    }
}

7、死锁

概述:

多个线程同时站有一些共享资源,并且相互等待其他线程占有的资源才能运行,而这导致两个或多个线程都在等待对方释放资源,都停止运行的情形;

某一个同步代码块同时拥有“两个以上对象的锁”时,就可能发生“死锁”;

又两个小朋友,小明和小强,小明有玩具汽车,但是小明想要小强的玩具枪,相反小强有玩具枪却想要小明的玩具汽车,双方都说如果你先把你的玩具给我,我就给你,所以两个小朋友都在等对方把玩具给自己,这就僵持住了,在程序中也就是“死锁”;

代码演示:
代码语言:javascript
复制
package com.zb.thread;

import lombok.SneakyThrows;

//测试死锁
public class TestDeadlock {
    public static void main(String[] args) {
        Object gan = new Object();
        Object car = new Object();
        new Thread(new Play("小明",gan,car)).start();
        new Thread(new Play("小强",gan,car)).start();
    }
}
class Play implements Runnable{

    private final String name;
    private final Object gan;
    private final Object car;

    public Play(String name, Object gan, Object car) {
        this.name = name;
        this.gan = gan;
        this.car = car;
    }

    @SneakyThrows
    @Override
    public void run() {
        if("小明".equals(name)){
            synchronized (car){//小明持有玩具车
                System.out.println("小明持有玩具车!");
                System.out.println("小明想要玩具枪!");
                synchronized (gan){
                    System.out.println("小明获得了玩具枪!");
                }
            }
        }else if("小强".equals(name)){
            synchronized (gan){//小强持有玩具枪
                System.out.println("小强持有玩具枪!");
                System.out.println("小强想要玩具车!");
                synchronized (car){
                    System.out.println("小明获得了玩具枪!");
                }
            }
        }
    }
}
运行结果:
代码语言:javascript
复制
小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!
修改代码,使得双方都愿意先让出自己的玩具:
代码语言:javascript
复制
package com.zb.thread;

import lombok.SneakyThrows;

//测试死锁
public class TestDeadlock {
    public static void main(String[] args) {
        Object gan = new Object();
        Object car = new Object();
        new Thread(new Play("小明",gan,car)).start();
        new Thread(new Play("小强",gan,car)).start();
    }
}
class Play implements Runnable{

    private final String name;
    private final Object gan;
    private final Object car;

    public Play(String name, Object gan, Object car) {
        this.name = name;
        this.gan = gan;
        this.car = car;
    }

    @SneakyThrows
    @Override
    public void run() {
        if("小明".equals(name)){
            synchronized (car){//小明持有玩具车
                System.out.println("小明持有玩具车!");
                System.out.println("小明想要玩具枪!");
            }
            Thread.sleep(1000);
            synchronized (gan){
                System.out.println("小明获得了玩具枪!");
            }
        }else if("小强".equals(name)){
            synchronized (gan){//小强持有玩具枪
                System.out.println("小强持有玩具枪!");
                System.out.println("小强想要玩具车!");
            }
            synchronized (car){
                System.out.println("小明获得了玩具枪!");
            }
        }
    }
}
运行结果:
代码语言:javascript
复制
小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!
小明获得了玩具枪!
小明获得了玩具枪!
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、线程安全问题概述
  • 二、线程安全问题的代码实现
    • 多线程类:
    • 测试类:
    • 运行结果(截取部分):
  • 三、线程安全问题产生的原理
  • 四、解决线程安全问题
    • 1、引入线程同步机制的三种方法
    • 2、同步代码块
      • 格式:
      • 锁对象的含义:
      • 注意:
      • 代码示例:
    • 3、同步代码块的原理
    • 4、同步方法
      • 使用步骤:
      • 格式
      • 代码示例:
      • 改进后的代码:
      • 备注:
    • 5、静态同步方法
      • 概述:
      • 注意:
    • 6、锁机制(Lock锁)
      • 概述:
      • 使用步骤:
      • 代码示例:
      • 代码示例更好的写法:
    • 7、死锁
      • 概述:
      • 代码演示:
      • 运行结果:
      • 修改代码,使得双方都愿意先让出自己的玩具:
      • 运行结果:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档