多线程基础——Synchronize

简介: 在Java中,`synchronized`关键字用于保证线程安全,通过加锁机制确保多线程环境下的原子性。它可以使线程由并行变为串行,虽然可能影响性能,但确保了结果的正确性。使用时需注意:1. **修饰方法**:锁住实例对象。2. **修饰代码块**:锁住当前调用方法的对象。3. **修饰静态方法**:锁住类对象。`synchronized`特性包括互斥、内存可见性和可重入。互斥确保同一时间只有一个线程能执行锁定代码;内存可见性保证线程间数据的一致性;可重入允许同一线程多次获取同一锁而不会死锁。

线程安全——Synchronized

[toc]

前面我们介绍了在Java中可以用加锁关键字synchronized保证原子性。

线程安全中通过synchronized给线程加锁,是线程由并行变为串行,这时可能会有疑问说:使用多线程的目的就是提高代码效率,加锁后就变成了单线程了,岂不是多此一举了?

因此在使用多线程时要注意一下几点

  1. 使用多线程的前提是必须保证结果的正确
  2. 在多线程修改共享变量时,才会出现线程安全问题;通过缩小锁的范围,从而提高程序的并发处理能力

1.使用方法

1.1修饰方法

这里我们以 :使用多线程实现count自增 为例

java

代码解读

复制代码

class Counter{
    public int count =0;
    public synchronized void increase(){
        count++;
    }
}

1.2修饰代码块

java

代码解读

复制代码

class Counter{
    public int count =0;
    public  void increase(){
        System.out.println("加锁之前的操作...");
        //锁对象 需提供一个对象
        synchronized (this){
            count++;
        }
        System.out.println("加锁之后的操作...");

    }
}

使用synchronized修饰的代码块涉及的指令,并不是在CPU上无脑的执行完,而是有可能执行了一半就被调度走了,但是即便被调度出CPU也不会释放锁,别的线程尝试获取锁时依然会阻塞等待

1.3锁静态方法

java

代码解读

复制代码

class Counter {
    public static int count = 0;
    public synchronized static void increase() {
        count++;
    }
}

1.3 给一个线程加锁,也会出现线程安全问题

举栗:

java

代码解读

复制代码

class Counter{
    public int count =0;

    /**
     * 执行自增
     */
    public synchronized void increase(){
            count++;
    }

    /**
     * 不加锁
     */
    public  void increase1(){
        count++;
    }
}

1.4锁对象

在1.2中**()里面的this指的是:是对那个对象进行加锁**

加锁操作,是针对 一个对象进行的。

而我们需要的理解的是:synchronized锁的是什么,当两个线程竞争的是同一个锁时,才会产生线程安全(两个线程竞争不同的锁时,根本不会产生任何的线程安全,两把锁之间没有任何关系,不会产生阻塞等待)。

因此在判断是否会产生锁竞争时,只需判断一下锁对象是否是同一个对象。

1.5 锁信息的记录

在Java虚拟机中,对象在内存结中的结构可以划分为4个区域:

  • markword:对象头,锁信息,GC,程序计数器
  • 类型指针(_class) :类型
  • 实例数据(instance_data) :成员变量信息
  • 对齐填充(padding)

总结:

synchronized的几种用法

  1. 修饰普通方法,相当于锁实例对象
  2. 对代码块进行加锁,相当于锁当前调用方法的对象(也是实例对象)
  3. 对静态方法进行加锁,相当于锁类对象

2.特性

互斥

一个线程获取了锁之后,其他线程就必须阻塞等待,等到当前获取锁的线程释放了锁之后再去竞争

内存可见(刷新内存)

synchronized实现内存可见的机制是通过原子性完成的,一个线程完成了读、改、写回内存这一操作之后,再释放锁,下一个线程才能获取锁,再开始读取

所以保证了下一个线程读到的值是上一个线程写回主内存的最新值

可重入

在方法调用的链路中,可能存在多个被synchronized修饰的方法或者代码块

对于同一对象,针对同一个线程(当前获得锁的线程)互斥那么这个锁就不可以重入,会产生一个互斥等待的现象,也叫死锁

对于同一锁对象,针对同一个线程(当前获得锁的线程)不互斥那么这个锁就可以重入


转载来源:https://juejin.cn/post/7299621996249694271

相关文章
|
8月前
|
Java 调度
【多线程和高并发】一:线程实现和synchronize
【多线程和高并发】一:线程实现和synchronize
|
缓存 Java
理论:第八章:线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包,ThreadLocal与Lock和Synchronize区别
理论:第八章:线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包,ThreadLocal与Lock和Synchronize区别
135 0
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(下)
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(下)
105 0
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(下)
|
安全
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(上)
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(上)
117 0
多线程编程核心技术-对象及变量的并发访问-synchronize同步方法(2)(上)
|
Java
java多线程基础(synchronize关键字)
基础知识 多线程实现方法 使用Thread创建线并启动线程 使用Runnable创建并启动线程 使用内部类创建线程 线程的方法 线程优先级 守护线程 sleep方法 yield方法 join方法 线程同步 基础知识 线程:进程(process)就是一块包含了某些资源的内存区域。
1053 0
|
19天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
48 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
66 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
47 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
29 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
47 2