【多线程】对象及变量的并发访问

synchronized同步方法

 

方法内的变量为线程安全

“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”,所得的结果也就是“线程安全”的了。

 

实例变量非线程安全

如果两个线程同时操作业务对象中的实例变量,则有可能出现“非线程安全”问题。

HasSellPrivateNum
public class HasSellPrivateNum {
    private int num = 0;

    public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over");
                Thread.sleep(2000);

            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + "num+=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
ThreadA
public class ThreadA extends Thread {

    private HasSellPrivateNum rumRed;

    public ThreadA(HasSellPrivateNum rumRed) {
        this.rumRed = rumRed;
    }

    @Override
    public void run() {
        super.run();
        rumRed.addI("a");

    }
}
ThreadB
public class ThreadB extends Thread {

    private HasSellPrivateNum rumRed;

    public ThreadB(HasSellPrivateNum rumRed) {
        this.rumRed = rumRed;
    }

    @Override
    public void run() {
        super.run();
        rumRed.addI("b");

    }
}
Run
public class Run {

    public static void main(String[] args) {
        HasSellPrivateNum numRed = new HasSellPrivateNum();

        ThreadA threadA = new ThreadA(numRed);
        threadA.start();

        ThreadB threadB = new ThreadB(numRed);
        threadB.start();

    }
}

运行结果

修改代码在方法前添加synchronized 关键字使结果同步

public class HasSellPrivateNum {
    private int num = 0;

    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over");
                Thread.sleep(2000);

            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + "num+=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

两个线程访问同一个对象中的同步方法时一定是线程安全的。

多个对象多个锁

Run

public class Run {

    public static void main(String[] args) {
        HasSellPrivateNum numRed1 = new HasSellPrivateNum();
        HasSellPrivateNum numRed2 = new HasSellPrivateNum();

        ThreadA threadA = new ThreadA(numRed1);
        threadA.start();

        ThreadB threadB = new ThreadB(numRed2);
        threadB.start();
    }
}

运行结果

从结果看,虽然使用了synchronized关键字,但是打印顺序确实不同步的,是交叉的。

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能程等待状态,前提是多个线程访问的是同一个对象。

如果多个线程访问多个对象,则JVM会创建多个锁。

 

调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的需要。

 

 

A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法

A线程先持有object对象的Lock锁,B线程如果这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

 

synchronize 锁重入

代码示例

创建Service类

public class Service {
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }
    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }

    synchronized public void service3(){
        System.out.println("service3");
    }

}

线程类MyThread

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        Service service = new Service();

        service.service1();
    }
}

运行类Run.java

public class Run {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

运行结果

关键字synchronize拥有锁重入的功能,也就在使用synchronized时,当一个线程得到一个对象锁后,再次请求对象锁时是可以再次得到该对象的锁的。这也就证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

 

“可重入锁”的概念是:自己可以再次获取自己的内部所。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的。如果不重入锁的话,就会造成死锁。

 

出现异常锁自动释放

同步不具有继承性

 

synchronized同步语句

synchronized方法是对当前对象进行加锁,而synchronized 代码块是对某一个对象进行加锁。

 

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

“synchronized(非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样可以得出以下三个结论:

1)当多个线程同时执行synchronized(x){}同步代码时程同步效果。

2)当其他线程执行X对象中的synchronized同步方法时呈同步效果。

3)当其他线程执行X对象方法里面的synchronized(this)代码块时也呈现同步效果。

 

 

synchronized关键字加到static静态方法上是给class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

数据类型String的常量池特性

两个线程持有相同的锁,所以造成锁不释放的问题。

 

volatile关键字

关键字volatile的主要作用是使变量在多个线程间可见。

测试项目

PrintString类

public class PrintString implements Runnable {

    private boolean isContinuePrint = true;

    public boolean isContinuePrint() {
        return isContinuePrint;
    }

    public void setContinuePrint(boolean continuePrint) {
        isContinuePrint = continuePrint;
    }

    public void printStringMethod() {
        try {
            while (isContinuePrint == true) {
                System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        printStringMethod();
    }
}

运行类Run

public class Run {

    public static void main(String[] args) {

        try {
            PrintString printString = new PrintString();
            new Thread(printString).start();
            System.out.println("我要停止它!stopThread="+Thread.currentThread().getName());
            printString.setContinuePrint(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使程序运行在服务器模式下,其效果是一直处于死循环状态,

停不下来的原因就是main线程一直在处理while()循环,导致程序不能继续执行后面的代码。

 

当JVM被设置成-server模式下时,

变量 isContinuePrint = true ,存在于公共堆栈及线程的私有栈中。在JVM设置为-server模式时为了线程运行的效率,线程一直在私有栈中取得isContinuePrint 的值是true。而代码printString.setContinuePrint(false);虽然被执行,更新的却是公共堆栈中的isContinuePrint 变量值false,所以一直就是死循环状态。

 

这个问题其实就是私有堆栈中的值和公共堆栈的值不同步造成的。解决这样的问题就要使用volatile关键字了,它主要的作用就是当线程访问isContinuePrint 这个变量时,强制性从公共堆栈中进行取值。

 

使用volatile关键字增加了实例变量在多个线程之间的可见性。

 

synchronized和volatile比较

  1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大的。
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现堵塞。
  3. volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为他会将私有内存和公共内存中的数据做同步。
  4. 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。

 synchronized代码块有volatile同步的功能

同步synchronize不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修饰效果。

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值