小言_互联网的博客

多线程之synchronized同步语句块(2)

343人阅读  评论(0)

1.synchronized代码块的同步性

在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其它线程对同一个object中所有气他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是同一个。
测试用例如下

package test;

/**
 * @Author LiBinquan
 */
public class ObjectService {
    public void serviceMethodA(){
        try{
            synchronized (this){
                System.out.println("A begin beginTime = "+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end     endTime = "+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public void serviceMethodB(){
            synchronized (this){
                System.out.println("B begin beginTime = "+System.currentTimeMillis());
                System.out.println("B end     endTime = "+System.currentTimeMillis());
            }
    }
}

线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private ObjectService service;
    public  ThreadTest1(ObjectService service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethodA();
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private ObjectService service;
    public  ThreadTest2(ObjectService service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethodB();
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        ObjectService service = new ObjectService();
        ThreadTest1 threadTest1 = new ThreadTest1(service);
        threadTest1.setName("A");
        threadTest1.start();
        ThreadTest2 threadTest2 = new ThreadTest2(service);
        threadTest2.setName("B");
        threadTest2.start();
    }
}

输出:

2.验证synchronized(this)代码块是锁定当前对象
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
测试代码如下

package test;

/**
 * @Author LiBinquan
 */
public class Task {
    public void otherMethod(){
        System.out.println("---------run  otherMethod---------");
    }
    public void doLongTimeTask(){
        synchronized (this){
            for (int i = 0; i < 10000; i++) {
                System.out.println("同步代码块  线程名 = "+Thread.currentThread().getName()+ " i = "+(i +1));
            }
        }
    }
}

线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private Task task;
    public  ThreadTest1(Task task){
        super();
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private Task task;
    public  ThreadTest2(Task task){
        super();
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.otherMethod();
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        ThreadTest1 threadTest1 = new ThreadTest1(task);
        threadTest1.start();
        Thread.sleep(100);
        ThreadTest2 threadTest2 = new ThreadTest2(task);
        threadTest2.start();
    }
}

输出结果:

由输出可以看出是非同步的。
修改一下task类里面的方法,如下:

在这里插入代码片


由输出可得是同步打印的。

3.将任意对象作为对象的监听器

多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的、阻塞的。
这说明synchronized同步方法或synchronized(this)同步代码块分别有两种作用。

  1. synchronized同步方法
    1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
    2)同一时间只有一个线程可以执行synchronized同步方法中的代码。
  2. synchronized(this)同步代码块
    1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
    2)同一时间只有一个线程可以执行synchronized(this)
    同步方法中的代码。

根据我之前讲到的,使用synchronized(this)格式来同步代码块,其实Java还支持对“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”来实现同步的功能。这个“任意对象”大多数是实例变量及方法参数,使用格式为synchronized(非this对象)。
根据前面对synchronized(this)同步代码块的作用总结可知,synchronized(非this对象)格式的作用只有1种:synchronized(非this对象)同步代码块。
1)在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。
2)当持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。
第一点论证代码如下

package test;

/**
 * @Author LiBinquan
 */
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()
                        + " 进入同步块");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + " 在 " + System.currentTimeMillis()
                        + " 离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}


线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private Service service;
    public  ThreadTest1(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.setUserNamePassWord("a","aa");
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private Service service;
    public  ThreadTest2(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.setUserNamePassWord("b","bb");
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadTest1 threadTest1 = new ThreadTest1(service);
        threadTest1.setName("A");
        threadTest1.start();
        ThreadTest2 threadTest2 = new ThreadTest2(service);
        threadTest2.setName("B");
        threadTest2.start();
    }
}

输出:

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

package test;

/**
 * @Author LiBinquan
 */
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()
                        + " 进入同步块");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + " 在 " + System.currentTimeMillis()
                        + " 离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

可见,使用synchronized(非this对象)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就是交叉运行。
下面再用另一个测试用例来验证2.

package test;

/**
 * @Author LiBinquan
 */
public class Service {

    private String anyString = new String();
    public void A(){
        try{
            synchronized (anyString) {
                System.out.println("A 开始");
                Thread.sleep(3000);
                System.out.println("A结束");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void B(){
        System.out.println("B 开始");
        System.out.println("B 结束");
    }
}

线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private Service service;
    public  ThreadTest1(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.A();
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private Service service;
    public  ThreadTest2(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.B();
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadTest1 threadTest1 = new ThreadTest1(service);
        threadTest1.setName("A");
        threadTest1.start();
        ThreadTest2 threadTest2 = new ThreadTest2(service);
        threadTest2.setName("B");
        threadTest2.start();
    }
}

输出:

由于对象监视器不同,所以运行结果就是异步的。
同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无需的,虽然在同步块中执行的顺序是同步的,这样极易出现“脏读”问题。
使用“synchronized(非this对象)同步代码块”格式也可以解决“脏读”问题。但在解决脏读问题之前,先做一个小测试,测试代码如下:

package test;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author LiBinquan
 */
public class MyList {
    private List list = new ArrayList();
    synchronized public void add(String userName){
        System.out.println("线程名称 :"+Thread.currentThread().getName()
                + "执行了add方法");
        list.add(userName);
        System.out.println("线程名称 :"+Thread.currentThread().getName()
                + "退出了add方法");
    }
    synchronized public int getSize(){
        System.out.println("线程名称 :"+Thread.currentThread().getName()
                + "执行了add方法");
        int sizeValue = list.size();
        System.out.println("线程名称 :"+Thread.currentThread().getName()
                + "退出了add方法");
       return sizeValue;
    }
}

线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private MyList list;
    public  ThreadTest1(MyList list){
        super();
        this.list = list;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10000; i++) {
            list.add("threadA = "+(i+1));
        }
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private MyList list;
    public  ThreadTest2(MyList list){
        super();
        this.list = list;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10000; i++) {
            list.add("threadB = "+(i+1));
        }
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyList list = new MyList();
        ThreadTest1 threadTest1 = new ThreadTest1(list);
        threadTest1.setName("A");
        threadTest1.start();
        ThreadTest2 threadTest2 = new ThreadTest2(list);
        threadTest2.setName("B");
        threadTest2.start();
    }
}

输出:

从运行结果来看,同步块中的代码是同步打印的,当前线程“执行”与“退出”是成对出现的,但是线程A和线程B的执行确实异步的,这就有可能出现脏读的环境。由于线程执行方法的顺序不确定,所以当A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读。
接下来以一段实际用例来说明:

package test;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author LiBinquan
 */
public class MyOneList {
    private List list = new ArrayList();
    synchronized public void add(String data){
        list.add(data);
    }
    synchronized public int getSize(){
        return list.size();
    }
}
package test;

/**
 * @Author LiBinquan
 */
public class Service {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try{
            if(list.getSize() < 1){
                Thread.sleep(2000);
                list.add(data);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}

线程1:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest1 extends Thread{
    private MyOneList list;
    public  ThreadTest1(MyOneList list){
        super();
        this.list = list;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.addServiceMethod(list,"A");
    }
}

线程2:

package test;


/**
 * @Author LiBinquan
 */
public class ThreadTest2 extends Thread{
    private MyOneList list;
    public  ThreadTest2(MyOneList list){
        super();
        this.list = list;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.addServiceMethod(list,"B");
    }
}

运行类:

package test;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyOneList list = new MyOneList();
        ThreadTest1 threadTest1 = new ThreadTest1(list);
        threadTest1.setName("A");
        threadTest1.start();
        ThreadTest2 threadTest2 = new ThreadTest2(list);
        threadTest2.setName("B");
        threadTest2.start();
        Thread.sleep(6000);
        System.out.println("ListSize = "+list.getSize());
    }
}

输出:

“脏读”出现了。出现的原因是两个线程异步的方式返回了list参数的size()大小。解决办法是“同步化”
这边修改Service类,代码如下:

package test;

/**
 * @Author LiBinquan
 */
public class Service {
    public MyOneList addServiceMethod(MyOneList list,String data){
        try{
            synchronized (list) {
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                    list.add(data);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}

由于list参数对象在项目中是一份实例,是单例的,而且也正需要对list参数的getSize()方法做同步的调用,所以就对list参数进行了同步化处理。
输出如下:


转载:https://blog.csdn.net/qq_27581753/article/details/105931798
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场