栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 并发总结, synchronized详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 并发总结, synchronized详解

Java 并发总结, synchronized详解
  • Java 并发总结, synchronized详解
    • 1 线程安全
    • 2 互斥同步
    • 3 synchronized特性
    • 4 synchronized的三种应用方式
      • 4.1 synchronized修饰实例方法
      • 4.2 synchronized作用于静态方法
      • 4.3 synchronized作用于代码块
    • 5 synchronized底层实现原理(待补充)
    • 6 Java三大变量的线程安全(待补充)

Java 并发总结, synchronized详解 1 线程安全

​ 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。那么这个对象就是线程安全的。

​ Java 语言中得各种操作共享数据可以分成五类,按安全程度由强到弱来排序:

1 不可变:不可变的对象一定是线程安全的,无论对象的方法实现还是方法的调用都不需要在采取措施。

​ 如果共享数据是一个基本数据类型,那么只要在定义的时候使用final 关键字修饰就可以保证它不可变;如果共享数据是一个对象,需要对象自行保证其行为不会对其状态产生任何影响。

2 绝对线程安全:绝对线程安全是不管运行环境如何,调用者都不需要任何额外的同步措施。通常需要付出很大的甚至不切实际的代价。

3 相对线程安全:就是通常意义的线程安全,确保这个对象单独的操作是安全的。Java语言中,大部分声称线程安全的类都属于这种类型,如Vector,Hashtable。

4 线程兼容:指的是本身对象并不是线程安全的,但是可以通过调用端的正确使用同步手段来保证对象的安全使用。通常我们说一个类不是线程安全的,指的就是这种状态,如:ArrayList,HashMap等。

5 线程对立:无论调用端是否采取了同步措施,都无法在多线程环境中并发使用。

2 互斥同步

互斥和同步是一种最常见、最主要的实现并发正确性(相对线程安全)的保障手段。

同步:指的是多个线程并发访问共享数据时,保证共享数据在同一个时刻只能被一个线程使用。

互斥:指的是实现同步的一种手段,临界区互斥量和信号量都是主要的互斥实现方式。

在java中,最基本的互斥同步手段就是synchronized关键字。synchronized可以保证线程竞争共享资源的正确性(多个线程并发访问共享数据时,保证共享数据在同一个时刻只能被一个线程使用)。

3 synchronized特性

原子性:确保线程互斥的访问同步代码。synchronized保证只有一个线程拿到锁,进入同步代码块操作共享资源,因此具有原子性。

可见性:保证共享变量的修改能够及时课件。执行synchronized时,会对应执行lock,unlock原子操作。lock操作,就会清空工作空间该变量的值;执行unlock操作之前,必须先把变量同步回主内存中。

有序性:synchronized内的代码和外部的代码禁止排序,至于内部的代码,则不会禁止排序,但是由于只有一个线程进入同步代码块,因此在同步代码块中相当于是单线程的,根据as-if-serial语义,即使代码块内部发生了重排序,也不会影响程序执行的结果。

悲观锁:synchronized是悲观锁,每次使用共享资源时都认为会和其他线程产生竞争,所以每次使用共享资源都会上锁。

独占锁:synchronized是独占锁,该锁一次只能被一个线程所持有,其他线程被阻塞。

非公平锁:synchronized是非公平锁,线程获取锁的顺序可以不按照线程的阻塞顺序,允许线程发出请求后立即尝试获取锁。

可重入锁:synchronized是可重入锁。持锁线程可以再次获取自己的内部的锁。

Tips:

1 悲观锁 or 乐观锁:是否一定要锁。

2 共享锁 or 独占锁(排他锁):是否可以有多个线程同时拿锁。

3 公平锁 or 非公平锁:是否按阻塞顺序拿锁。

4 可重入锁 or 不可重入锁: 拿锁线程是否可以多次拿锁。

4 synchronized的三种应用方式 4.1 synchronized修饰实例方法

作用于给当前实例加锁,进入同步代码前要获得当前实例的锁。(注意:是当前实例,一个类有N个实例,一个实例有一把锁)。

使用synchronized 修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着,且共享对象一定是this,并且同步代码块是整个方法体。

示例1

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addI(String username){
        try {
            if ("a".equals(username)){
                num = 100;
                System.out.println("a set over!   " + System.currentTimeMillis());
                Thread.sleep(2000);
            }else {
                num = 200;
                System.out.println("b set over!   " + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}
public class ThreadB extends Thread{
    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}
public class TestRun {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef1);
        athread.start();
        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();
    }
}

运行结果如下:

synchronized numRef1 和 numRef2 是两个不同的类,同步方法的锁对象不是同一个对象,无法达到同步的目的,解决方法是让synchronized 锁对象是同一个,示例代码如下,运行结果如下图。

public class TestRun {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        //HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef1);
        athread.start();
        ThreadB bthread = new ThreadB(numRef1);
        bthread.start();
    }
}

运行结果如图:

4.2 synchronized作用于静态方法

表示找类锁,类锁永远只有一把,就算创建了100个对象,那类锁也只有一把。

对象锁:一个对象一把锁,

类锁:100个对象,也是一把锁。

当synchronized 作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized 而线程B需要调用这个实例对象所属类的synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized 方法占用的锁是当前类的class对象而访问非静态synchronized 方法占用的锁是当前实例对象锁。

public class Service {

    synchronized public static void printA(){
        try {
            System.out.println("线程名称为:  " + Thread.currentThread().getName() + "   " + System.currentTimeMillis() + "   进入printA ");
            Thread.sleep(3000);
            System.out.println("线程名称为:  " +Thread.currentThread().getName() + "   " + System.currentTimeMillis() + "   离开printA ");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    synchronized public static void printB(){
        System.out.println("线程名称为:  "+Thread.currentThread().getName() + "   在 " + System.currentTimeMillis()  + "  进入printB");
        System.out.println("线程名称为:  "+Thread.currentThread().getName() + "   在 " + System.currentTimeMillis()  + "  离开printB");
    }

}
public class ThreadA extends Thread{
    @Override
    public void run() {
        Service.printA();
    }
}
public class ThreadB extends Thread{
    @Override
    public void run() {
        Service.printB();
    }
}
public class TestRun {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
    }
}

运行结果如下:

synchronized(class) 代码块的作用其实和synchronized static 方法的作用一样。

public class Service {

    public static void printA(){
        synchronized (Service.class){
            try {
                System.out.println("线程名称为:  " + Thread.currentThread().getName() + "   " + System.currentTimeMillis() + "   进入printA ");
                Thread.sleep(3000);
                System.out.println("线程名称为:  " +Thread.currentThread().getName() + "   " + System.currentTimeMillis() + "   离开printA ");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    synchronized public static void printB(){
            System.out.println("线程名称为:  "+Thread.currentThread().getName() + "   在 " + System.currentTimeMillis()  + "  进入printB");
            System.out.println("线程名称为:  "+Thread.currentThread().getName() + "   在 " + System.currentTimeMillis()  + "  离开printB");
    }
}
public class ThreadA extends Thread{

    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends Thread{

    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}
public class TestRun {
    public static void main(String[] args) {

        Service service1 = new Service();
        Service service2 = new Service();
        ThreadA a = new ThreadA(service1);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service2);
        b.setName("B");
        b.start();
    }
}

运行结果如下:

4.3 synchronized作用于代码块

synchronized 修饰代码块,指定加锁对象,对给定对向加锁,进入同步代码库前要获得给定对象的锁。在某些情况下,我们编写的方法体比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:

public class ObjectService {
    public void serviceMethod(){
        try {
            synchronized (this){
                System.out.println(Thread.currentThread().getName() + "    begin time  " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "    end time  "+ System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private ObjectService service;

    public ThreadA(ObjectService service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethod();
    }
}
public class ThreadB extends Thread{
    private ObjectService service;

    public ThreadB(ObjectService service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethod();
    }
}
public class TestRun {
    public static void main(String[] args) {
        ObjectService service = new ObjectService();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

运行结果如下:

synchronized(this) 代码块是锁定当前对象的,证明项目如下:

public class Task {
    public void otherMethod(){
        System.out.println(Thread.currentThread().getName() + "------------------run---otherMethod");
    }

    public void doLongTimeTask(){
        synchronized (this){
            for (int i = 0; i< 10000; i++){
                System.out.println("synchronized   threadName = " + Thread.currentThread().getName() + "   i = " + (i + 1));
            }
        }
    }
}
public class MyThread1 extends Thread{
    private Task task;

    public MyThread1(Task task) {
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }
}
public class MyThread2 extends Thread{
    private Task task;

    public MyThread2(Task task) {
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.otherMethod();
    }
}
public class TestRun {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        MyThread1 thread1 = new MyThread1(task);
        thread1.start();
        Thread.sleep(10);
        MyThread2 thread2 = new MyThread2(task);
        thread2.start();
    }
}

运行结果如下:

修改Task.java如下:

public class Task {
    synchronized public void otherMethod(){
        System.out.println(Thread.currentThread().getName() + "------------------run---otherMethod");
    }

    public void doLongTimeTask(){
        synchronized (this){
            for (int i = 0; i< 10000; i++){
                System.out.println("synchronized   threadName = " + Thread.currentThread().getName() + "   i = " + (i + 1));
            }
        }
    }
}

运行结果如下:

多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的,阻塞的。synchronized同步方法和synchronized(this)都是当前对象锁。

synchronized(非this对象)格式的作用只有一种:synchronized(非this对象x)同步代码块。

public class Service {
    private String usernameParam;
    private String passwordParam;
    private String anyString = new String();

    public void setUsernamePassword(String username, String password) {
        try {
            synchronized (anyString){
                System.out.println("线程名称为:   " + Thread.currentThread().getName() + "  在  " + System.currentTimeMillis() + "进入同步块");
                usernameParam = username;
                Thread.sleep(3000);
                passwordParam = password;
                System.out.println("线程名称为:   " + Thread.currentThread().getName() + "  在  " + System.currentTimeMillis() + "离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.setUsernamePassword("a","a");
    }
}
public class ThreadB extends Thread{
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.setUsernamePassword("b","b");
    }
}
public class TestRun {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

运行结果如下

锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这是虽然能实现同步,但会受到阻塞,影响执行效率;但如果使用同步代码块锁非this对象,则synchronized(非this) 代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可提高运行效率。

修改Service.java 文件代码如下:

public class Service {
    private String usernameParam;
    private String passwordParam;

    public void setUsernamePassword(String username, String password) {
        try {
            String anyString = new String();
            synchronized (anyString){
                System.out.println("线程名称为:   " + Thread.currentThread().getName() + "  在  " + System.currentTimeMillis() + "进入同步块");
                usernameParam = username;
                Thread.sleep(3000);
                passwordParam = password;
                System.out.println("线程名称为:   " + Thread.currentThread().getName() + "  在  " + System.currentTimeMillis() + "离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下:

使用synchronized(非this对象x)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。

5 synchronized底层实现原理(待补充) 6 Java三大变量的线程安全(待补充)

实例变量:在堆中

静态变量:在方法区

局部变量:在栈中

局部变量在栈中,永远都不会存在线程安全问题,因为局部变量不共享。(一个线程一个栈)

实例变量在方法区中,方法区只有一个

堆和方法区都是多线程共享的,所以可能存在线程安全问题。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/830578.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号