Java synchronized笔记

简介:

1,造成线程安全问题的原因
①存在线程间的共享数据(例如堆内存)
②存在多个线程同时操作共享数据

2,锁类型
互斥锁:(重量级锁)同一时刻有且仅有一个线程操作共享数据,当某个线程正在操作共享数据时,其他线程处于等待状态,必须等到该线程处理完后再进行.
偏向锁:java1.6的新锁,针对多个线程竞争较少的场景,如果一个线程获得了锁,那么锁就进入偏向模式,对象头Mark Word里面的锁结构也编程偏向锁结构,这个线程再次请求锁时,无需做任何操作。
轻量级锁:依据是大部分的锁,整个同步周期内不存在竞争,适用线程交替执行同步块的场景。
自旋锁:依据是大部分线程持有锁的时间不会太长,因此自旋锁假设很快当前线程就会获得锁,所以虚拟机会让想要获取锁的线程执行几个空循环,如果循环完成后得到锁,则顺利进入。否则就会将线程在操作系统层面挂起。
锁消除:指虚拟机在编译时消除无用的锁。

3,synchronized关键字作用:
①保证同一时刻只有一个线程可以执行某个方法或者代码块
②可以保证一个线程操作的共享数据变化能立即被其他线程所见(可替代volatile)

4,synchronized关键字用法:
①修饰实例方法:作用于当前实例的方法(不包括静态方法),进入同步代码前要获取当前实例的锁(JVM通过常量池中的方法结构表的acc_synchronized标识区分一个方法是否是同步方法。)
public class SynchronizedClass extends Thread
{

static int share = 0;
//①修饰实例方法
public synchronized void increase()
{
    share++;
}

//②修饰静态方法
public synchronized static void increase4ClassLock(){
    share++;
}
public void run()
{
    for (int i = 0; i < 1000; i++)
    {
        increase();
    }
}

/**
 * @param args
 */
public static void main(String[] args)
{
    try
    {
        //场景1,t1,t2线程执行需要获得实例sc1的互斥锁
        SynchronizedClass sc1 = new SynchronizedClass();
        Thread t1 = new Thread(sc1);
        Thread t2 = new Thread(sc1);
        t1.start();
        t2.start();
        // join()方法:当前线程结束之后join()方法才返回,如果不试用join,打印出来的数据可能是0,1000,2000
        t1.join();
        t2.join();
        System.out.println("scene1 share:" + share);
        
        //share = 0;
        //场景2,t3,t4线程执行各自获得实例sc2,sc3的锁,可能同时访问共享的静态资源share出现线程安全问题
        SynchronizedClass sc2 = new SynchronizedClass();
        SynchronizedClass sc3 = new SynchronizedClass();
        Thread t3 = new Thread(sc2);
        Thread t4 = new Thread(sc3);
        t3.start();
        t4.start();
        t3.join();
        t4.join();
        System.out.println("scene2 share:" + share);
    } catch (Exception e)
    {
        e.printStackTrace();
    }
}

}
上面例子中:当前线程的锁是实例对象instance,java的线程同步锁可以是任意对象。注意:如果一个线程正在访问一个实例instance的synchronized方法A时,其他线程不能访问该实例的其他synchronized方法,因为一个对象实例只有一把锁,但其他线程可访问该实例的非synchronized方法。当然,如果其他线程要访问另外一个实例instance1的任何方法都是不受限制的,但是如果他们访问的是共享数据那么就会出问题,例如上面例子场景2中的静态变量share,这时候就需要使用②进行修饰。
②修饰静态方法:作用于当前类对象,进入同步代码前要获得当前类(class)对象的锁。注意:两个线程可以同时分别访问static synchronized修饰的方法和non-static synchronized修饰的方法,因为他们一个是类对象的锁,一个是实例对象的锁,但是此时两个方法如果同时操作共享数据,也可能出现线程安全问题。例如上面例子中两个线程分别同时访问:increase4ClassLock()和increase()方法,就会出现线程安全问题。
③修饰代码块:指定加锁对象,给指定对象家锁,进入同步代码块前要获取指定对象的锁(可以是类对象,也可以是实例对象),样例如下:
static SynchronizedClass inst1 = new SynchronizedClass();
public void increase2MethodBlock1(){

//使用实例锁,获取当前对象实例的锁,inst1也可用this(synchronized (inst1))
synchronized (inst1)
{
    share++;
}

}
public void increase2MethodBlock2(){

//使用类对象锁,获取当前class对象的锁
synchronized (SynchronizedClass.class)
{
    share++;
}

}

5,sychronized实现原理
jvm中synchronized的同步是通过管程(Monitor)实现的,在java的堆内存中,一个对象实例信息是按照如下方式存储的:

image

如上如所示结构,java对象头里面有锁信息,锁信息里有一个指向monitor(管程)对象的指针,管程对象在java虚拟机中是使用OjbectMonitor类实现的,类结构大致如下:
ObjectMonitor(){
_count = 0; //计数器
_WaitSet = null;
_EntryList = null;
_owner = null; //指向拥有ObjectMonitor对象的线程
……
}
说明:
①多个线程进入同步代码时,会先进入_ EntryList队列,获取到monitor对象后修改owner指向为当前对象线程,计数器加1
②线程调用wait()方法进入_WaitSet队列,owner置为空,count减1
③当前线程执行完毕也会重置owner为空,复位变量
image

6,synchronized需要注意的点
①可重入性
一个线程在synchronized方法体内部可以调用另外一个synchronized方法,因为是同一个线程请求同一个锁。子类继承父类时,子类可通过重入锁调用父类的synchronized方法。
②在线程sleep时调用interrupt方法会打断阻塞并抛出异常(对阻塞线程进行中断操作):
image

③处于非阻塞状态的线程需要我们手动进行中断检测并结束程序:
image

如上②和③两种情况(阻塞/和线程运行时中断中断的操作方法是不一样的[前者要捕捉异常,后者要手动判断中断状态并做中断处理]),可用下面代码兼顾两种场景:
image

④线程状态转换
image
1)等待阻塞:等待阻塞(使用wait,notify/notifyAll方法进入和退出)(wait是Object类的方法)
2)锁定阻塞:synchronized同步锁的获得和释放
3)其他阻塞:sleep,join,IO操作等(sleep是Thread类的静态方法)
4)就绪状态获得时间片就进入运行态,时间片用完或yield操作回到就绪状态
5)sleep和wait的区别:sleep方法是Thread类的今天方法而wait是Object类的方法;sleep方法不会释放对象锁,sleep时其他线程任然不能访问同步块,而wait方法会释放对象锁。
image
⑤线程中断操作对正在等待获取锁的synchronized方法或者代码块是不起作用的:一个正在等待锁的线程要么获得锁继续执行,要么继续等待。
image

⑥wait(); notify(); notifyAll();方法必须放在synchronized中,否则会抛出IllegalMonitorStateException异常,因为:调用这几个方法必须获取管程(ObjectMonitor)对象,该对象放在对象头中,而synchronized可以获取到该对象。

目录
相关文章
|
8天前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
9 0
|
8天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
13 0
|
1月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--希尔排序
数据结构与算法(Java篇)笔记--希尔排序
|
1月前
|
存储 Java 程序员
记一次synchronized锁字符串引发的坑兼再谈Java字符串
记一次synchronized锁字符串引发的坑兼再谈Java字符串
20 2
|
2月前
|
监控 负载均衡 Dubbo
|
12天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
1月前
|
安全 Java
Java并发编程:Synchronized及其实现原理
Java并发编程:Synchronized及其实现原理
23 4
|
1天前
|
Java
浅谈Java的synchronized 锁以及synchronized 的锁升级
浅谈Java的synchronized 锁以及synchronized 的锁升级
5 0
|
8天前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
9 0
|
1月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--快速排序
数据结构与算法(Java篇)笔记--快速排序