Java(72):多线程学习03-->解决多线程安全—synchronized

根据《Java(71):多线程学习02-->实现Runnable接口方式实现多线程》一文中1.2【数据资源共享】章节,数据资源共享会遇到多线程不安全问题,多个线程同时操作同1个对象,如果控制不好,就会产生问题,叫做线程不安全,可以用synchronized来解决。

synchronized简介

同步锁,synchronized相当于给方法,对象上锁或者给类上锁,这样防止其他线程访问共享资源,进而保护多线程的安全。synchronized的原理是它使用了flag标记ACC_SYN-CHRONIZED,执行线程先持有同步锁,然后执行方法,最后在方法完成时才释放锁。

synchronized使用

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

1、synchronized修饰代码块

修饰代码块:指定加锁对象,对给定对象/类加锁。synchronized(this / object) 表示进入同步代码库前要获得给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得当前 class 的锁。

synchronized(this) {
               //代码块
            }

示例:

class RunnableTest  implements Runnable{
    private int ticket =10;
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(this) {
                if (this.ticket > 0) {
                    System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
                }
            }
        }

    }
}

在调用synchronized修饰的代码块前,要加上等待Thread.sleep(10);否则都会让1个线程都占用了。

调用:

public class RunnableDemo {
    public static void main(String[] args){
        RunnableTest rt=new RunnableTest();
        Thread t1 = new Thread(rt,"一号窗口");
        Thread t2 = new Thread(rt,"二号窗口");
        Thread t3 = new Thread(rt,"三号窗口");
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果:

Java(72):多线程学习03-->解决多线程安全—synchronized_第1张图片

当编写的方法体比较大时,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。将synchronized作用于一个给定的实例对象t,即当前实例对象就是锁对象每次当线程进入synchronized包裹的代码块时就会要求当前线程持有sell实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待。当然除了t作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁。

2、synchronized修饰方法

修饰实例方法:作用于当前对象实例方法加锁,进入同步代码前要获得当前对象实例方法的锁。

synchronized void method() {
  //业务代码
}

示例:

class RunnableTest  implements Runnable{
    private int ticket =10;
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(10);
                sell();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    synchronized private void sell()  {
        if (this.ticket > 0) {
            System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
        }
    }

}

在调用synchronized修饰的sell方法前,要加上等待Thread.sleep(10);否则都会让1个线程都占用了。

调用:

public class RunnableDemo {
    public static void main(String[] args){
        RunnableTest rt=new RunnableTest();
        Thread t1 = new Thread(rt,"一号窗口");
        Thread t2 = new Thread(rt,"二号窗口");
        Thread t3 = new Thread(rt,"三号窗口");
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果:

数据资源共享,10张票3个窗口(线程执行)来完成。

Java(72):多线程学习03-->解决多线程安全—synchronized_第2张图片

当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的所有 synchronized 方法,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的synchronized实例方法,这样的方式也就保护了多线程的安全,不过其他线程还是可以访问该实例对象的其他非synchronized方法。

其他:

1、 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 
2、每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 
3、实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制

建议:尽量减小synchronized代码块的大小。

参考:Java多线程(四) 解决多线程安全

https://zhuanlan.zhihu.com/p/412782802

https://www.cnblogs.com/weibanggang/p/9470718.html

你可能感兴趣的:(java相关,git,java,github)