Java多线程二——对象及变量的并发访问(概念理解)

Java多线程二——对象及变量的并发访问(概念理解)

在java多线程的使用过程中,我们面临的最现实的问题就是"非线程安全的问题"——多线程导致的数据不同步问题。
解决上述非线程安全问题的方法:
  • 如果不同步问题是由某个变量引起的,那么该变量必为某一个类的全局变量,这时候如果可以将它改为线程所调用方法内的局部变量就可以解决数据不同步的问题。
  • 2.在线程所调用的方法上加synchronized关键字。这是解决线程不同步问题最常用的方法。有 synchronized关键字的方法是同步方法。
    注意事项:
    synchronized关键字不可以加在继承于Thread类的子类的run()方法上,因为如果有当前线程类的多个对象同时被开启时,每个对象都具有各自的对象锁,都可以执行各自的run方法。这样就起不到同步的作用了。
    如果非要在run()方法上加synchronized关键字,唯一的做法就是不要去继承Thread类,转而实现Runnable()接口。
脏读:在读取实例变量前,此值可能已经被别的线程更改了。

synchronized方法与锁对象

  • 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其它线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其它的非synchronized同步方法。
  • 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其它线程必须等A线程执行完才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁之后才可以调用。
  • 关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到此对象锁的。这也就证明在一个synchronized方法内部调用本类其他synchronized方法时,是永远可以得到锁的。(可重入锁:指自己可以再次获取自己的内部锁)当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。下面,我将展示一个例子:
/**
 *   父类
 */
public class Main {
    public int i=10;
    synchronized public void operateIMainMethod(){
        try {
            i--;
            System.out.println("main print i="+i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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



/**
 * 子类
 */
class Sub extends Main{
    synchronized public void operateISubMethod(){
        try {
            while(i>0){
                i--;
                System.out.println("sub print i="+i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 线程类
 */
class Mythread extends Thread{
    @Override
    public void run() {
        super.run();
        Sub sub=new Sub();
        sub.operateISubMethod();
    }
}

运行结果:
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
同步与异步的区别:
* 当有多个线程同时访问某一个代码片段时,这个代码片段中有一部分代码是包含在由synchronized 修饰的同步代码块中,而另一部分则在同步代码块之外,则它们执行同步代码块中的代码被称为同步,执行同步代码块之外的代码时,被称为异步。
* 同步,同一时间只允许一个线程访问
* 异步,同一时间允许多个线程同时访问(逻辑上的同时,物理上不一定是同时的)

同步方法与同步代码块之间需要注意的地方:
**如果在一个类中有很多个synchronized同步方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率**

* 1当同步代码块中的对象为this对象时:
* 对其他用synchronized 修饰的方法或synchronized(this)同步代码块呈阻塞状态,同一时间只有一个线程可以执行同步方法或者同步代码块中的代码,即同步代码块与同步方法是同步的。

  • 2.当同步代码块中使用的对象为非this对象时:
  • 同步代码块的执行和同步方法的执行之间是异步的。
关于synchronized(非this对象x)使用的三个结论:
  • 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果;
  • 当其他线程执行x对象中synchronized同步方法时呈同步效果;
  • 当其他线程执行x对象方法里面的synchronized (this)代码块时也呈现同步效果。
关于静态同步synchronized方法与synchronized(class)代码块
synchronized关键字加到非static方法上是给对象上锁;synchronized加到static方法上是给Class类上锁。对象锁只对当前对象起作用,而Class锁则对类的所有对象起作用。

* 对象锁:synchronized(this)或者synchronized(A a)
* Class锁:synchronized(A.class)

数据类型String的常量池对多线程的影响

先看个例子:

public class StringTest{

    public void print(String s) {
        synchronized (s) {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "--------");
            }
        }
    }


    public  static void main(String []args){
        StringTest st=new StringTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                st.print("a");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                st.print("a");
            }
        }).start();
    }
}

运行的结果为:
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
由运行结果可以看到,只有一个线程被执行了,而另一个线程并没有被执行,这其实就是String常量池的原因,两个线程的run方法中调用print()方法时传入的参数都是”a”两个线程需要同一个锁对象,而Thread-0持有锁对象,就进入死循环,导致线程Thread-1得不到执行。当我们将String参数改变为Object对象时,就可以解决上述问题,如下所示:

public class StringTest{

    public void print(Object object) {
        synchronized (object) {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "--------");
            }
        }
    }


    public  static void main(String []args){
        StringTest st=new StringTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                st.print(new Object());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                st.print(new Object());
            }
        }).start();
    }
}

运行结果为:
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–

死锁:
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

产生死锁的现象:
* synchronized嵌套的代码结构:

synchronized(lock1){
    ...
    synchronized(lock2){
        ...
    }
    ...
}
关键字volatile
关键字volatile的主要作用是使变量在多个线程间可见。它的作用是强制从公共堆栈中取得变量的值,而不是从线程的私有数据栈中取得变量的值。

我们来看一个例子:

public class VolatileTest extends Thread{

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("进入run了");
        while(isRunning==true){

        }
        System.out.println("线程被停止了");
    }

    public  static void main(String []args){

        try {
            VolatileTest vt=new VolatileTest();
            vt.start();
            Thread.sleep(1000);
            vt.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果为:
进入run了
已经赋值为false
由此可以断定程序进入了死循环当中,(原因:在启动VolatileTest .java线程时,变量private boolean isRunning = true;存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程的运行效率,线程一直在私有堆栈中进行取值。而代码vt.setRunning(false);虽然被执行,但它更新的是公共堆栈中的数据,线程的私有堆栈数据并没有与之一起变化,最终导致了死循环)下面我将通过Volatile关键字来解决此问题(Volatile关键字会在线程访问上述代码中的isRunning 时,强制从公共堆栈中进行取值):

public class VolatileTest extends Thread{

    volatile private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("进入run了");
        while(isRunning==true){

        }
        System.out.println("线程被停止了");
    }

    public  static void main(String []args){

        try {
            VolatileTest vt=new VolatileTest();
            vt.start();
            Thread.sleep(1000);
            vt.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果为:
进入run了
已经赋值为false
线程被停止了
根据运行结果我们会发现,之前的问题已经被我们解决了。在这里我告诉大家,其实synchronized同样具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。
我们对之前的错误代码做出一下修改,看一看运行的效果:

public class VolatileTest extends Thread{

    private boolean isRunning = true;

    public boolean isRunning() {

        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("进入run了");
        String anything=new String();
        while(isRunning==true){

            synchronized (anything){

            }

        }
        System.out.println("线程被停止了");
    }

    public  static void main(String []args){

        try {
            VolatileTest vt=new VolatileTest();
            vt.start();
            Thread.sleep(1000);
            vt.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果为:
进入run了
已经赋值为false
线程被停止了
从结果来看synchronized确实起到了私有变量与公共变量的同步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值