java多线程系列(二)---对象变量并发访问

对象变量的并发访问

前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,但愿站在巨人的肩膀上,再经过个人理解能让知识更加简单易懂。html

目录

线程安全

  • 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其余线程不能进行访问直到该线程读取完,其余线程才可以使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程前后更改数据形成所获得的数据是脏数据

局部变量并不会数据共享

public class T1 {
    public static void main(String[] args) {
        PrivateNum p=new PrivateNum();
        MyThread threadA=new MyThread('A',p);
        MyThread threadB=new MyThread('B',p);
        threadA.start();
        threadB.start();
    }}
    class MyThread extends Thread
    {
        char i;
        PrivateNum p;
        public MyThread(char i,PrivateNum p)
        {
            this.i=i;
            this.p=p;
        }
        public void run()
        {
            p.test(i);
        }
    }
    class PrivateNum
    {
        
        public void test( char i) 
        {
            try {
                int num=0;
                if(i=='A')
                {
                    num=100;
                    
                    System.out.println("线程A已经设置完毕");
                    Thread.sleep(1000);
                }
                else
                {
                    num=200;
                    System.out.println("线程B已经设置完毕");
                }
                System.out.println("线程"+i+"的值:"+num);
                
            }
         catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }}
    }
    
    线程A已经设置完毕
    线程B已经设置完毕
    线程B的值:200
    线程A的值:100
  • 在这段代码中,线程A和B前后对num进行赋值,当两个线程都赋值后再分别打印,可是输出的结果并不相同,后赋值的线程并无对num的值进行覆盖,由于这里的num是在方法里面的,也就是局部变量,不一样线程的num并不共享,因此并不会发生覆盖。

实例成员变量数据共享

public void test( char i) 
        {
            int num=0;
            try {
            
                if(i=='A')
                {
                    num=100;
                    
                    System.out.println("线程A已经设置完毕");
                    Thread.sleep(1000);
                }
                else
                {
                    num=200;
                    System.out.println("线程B已经设置完毕");
                }
                System.out.println("线程"+i+"的值:"+num);
                
            }
         catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }}
        线程B已经设置完毕
    线程A已经设置完毕
    线程B的值:200
    线程A的值:200
  • 这里的代码只是将int num=0放到了方法的外面,可是输出的结果却不一样,由于这时候线程AB访问的是同一个变量(指向同一个地址),因此这个时候会发生覆盖,同时这里出现了线程安全问题。

synchronized关键字能够避免线程安全问题

public synchronized void test( char i) 
        {
            int num=0;
            try {
            
                if(i=='A')
                {
                    num=100;
                    
                    System.out.println("线程A已经设置完毕");
                    Thread.sleep(1000);
                }
                else
                {
                    num=200;
                    System.out.println("线程B已经设置完毕");
                }
                System.out.println("线程"+i+"的值:"+num);
                
            }
         catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }}
    线程A已经设置完毕
    线程A的值:100
    线程B已经设置完毕
    线程B的值:200
  • 这里只是上面代码的基础上增长了一个synchronized ,避免了线程安全问题

总结

  • 若是多个线程访问的是同一个对象方法中的局部变量,那么这个变量并不共享,线程AB对此变量的操做将互不影响
  • 若是多个线程访问的是同一个对象方法中的成员变量,那么这个变量共享,若是不处理好线程问题,可能会出现线程安全问题
  • 经过synchronized关键字可使方法同步

多个线程访问的是两个不一样实例的同一个同步方法

public class T1 {
    public static void main(String[] args) {
        PrivateNum p1=new PrivateNum();
        PrivateNum p2=new PrivateNum();
        MyThread threadA=new MyThread('A',p1);
        MyThread threadB=new MyThread('B',p2);
        threadA.start();
        threadB.start();
    }}
    线程A已经设置完毕
    线程B已经设置完毕
    线程B的值:200  
    线程A的值:100
  • 这里的代码又是在上面的代码进行修改,这里咱们添加了synchronized关键字,对方法上锁,可是倒是异步执行的(同步的话,应该是这样输出 线程A已经设置完毕 线程A的值:100 ),这是由于这里是两个锁,建立了p1和p2对象,建立的是两个锁,锁对象不一样不形成互斥做用。

多线程调用同一个实例的两个不一样(一个同步,一个非同步)方法

public class T1 {
    public static void main(String[] args) {
        PrivateNum p1=new PrivateNum();
    
        MyThread threadA=new MyThread('A',p1);
        MyThread2 threadB=new MyThread2('B',p1);
        threadA.start();
        threadB.start();
    }}
    class MyThread extends Thread
    {
        char i;
        PrivateNum p;
        public MyThread(char i,PrivateNum p)
        {
            this.i=i;
            this.p=p;
        }
        public void run()
        {
            p.test(i);
        }
    }
    class MyThread2 extends Thread
    {
        char i;
        PrivateNum p;
        public MyThread2(char i,PrivateNum p)
        {
            this.i=i;
            this.p=p;
        }
        public void run()
        {
            p.test2(i);
        }
    }
    class PrivateNum
    {
        int num=0;
        public void test2(char i)
        {
            System.out.println("线程"+i+"执行,线程A并无同步执行");
        }
        public synchronized void  test( char i) 
        {
            
            try {
                
                if(i=='A')
                {
                    num=100;
                    
                    System.out.println("线程A已经设置完毕");
                    Thread.sleep(100);
                }
                else
                {
                    num=200;
                    System.out.println("线程B已经设置完毕");
                }
                System.out.println("线程"+i+"的值:"+num);
                
            }
         catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }}
    }
    线程A已经设置完毕
    线程B执行,线程A并无同步执行
    线程A的值:100
    线程B的值:200
  • 这里的代码咱们给PrivateNum添加了一个非同步的test2方法,MyThreadrun中调用的同步的test方法,MyThread2中调用的是非同步的test2方法,实验代表线程B能够异步调用非同步的方法。

多线程调用同一个实例的两个不一样的同步方法

public synchronized void test2(char i)
        {
            System.out.println("线程"+i+"执行,线程A同步执行");
        }
    线程A已经设置完毕
    线程A的值:100
    线程B执行,线程A同步执行
    线程B的值:200
  • 这里的代码咱们只是给test2方法添加一个synchronized关键字,这个时候两个线程调用的方法同步执行。

总结

  • 多个线程调用的不一样实例的同步方法,线程不互斥。
  • 若是两个线程的锁对象同样(都是p1),两个线程分别调用同步方法和非同步方法,线程不会同步执行。
  • 可是若是调用两个不一样的同步方法,由于锁对象一致,两个线程同步执行。
  • 设想一个状况,有一个实例有两个方法,一个修改值(synchronized),一个读值(非synchronized),此时两个线程一个修改值,一个读取值,这个时候由于这两个线程并不会挣抢锁,两个线程互不影响,那么此时可能就会出现一种状况,线程A还没修改完,线程B就读取到没有修改的值。这就是所谓的脏读。

重入锁

public class T1 {
    public static void main(String[] args) {

        MyThread3 thread=new MyThread3();
        thread.start();
    }}
    class MyThread3 extends Thread
    {
        Service s=new Service();
        public void run()
        {
            s.service1();
        }
    }
    class Service
    {
        public synchronized void service1()
        {
            System.out.println("服务1并无被锁住");
            service2();
        }
        public synchronized void service2()
        {
            System.out.println("服务2并无被锁住");
            service3();
        }
        public synchronized void service3()
        {
            System.out.println("服务3并无被锁住");
        }
    }
    服务1并无被锁住
服务2并无被锁住
服务3并无被锁住
  • 咱们可能会这么认为,thread线程执行了同步的service1方法,这个时候把锁占住,若是这个时候要执行另外一个同步方法service2方法,必须先执行完service1方法,而后把锁让出去才行,可是实验证实锁是能够重入的,一个线程得到锁后,还没释放后能够再次获取锁。

出现异常会释放锁

  • 若是同步方法里面出现异常,会自动将锁释放

同步方法不会继承

public class T1 {
    public static void main(String[] args) {

        Service3 s=new Service3();
        MyThread4 t1=new MyThread4(s,'1');
        MyThread4 t2=new MyThread4(s,'2');
        t1.start();
        t2.start();
        
    }}
    class MyThread4 extends Thread
    {
        Service3 s;
        char name;
        public MyThread4(Service3 s,char name)
        {
            this.s=s;
            this.name=name;
        }
        public void run()
        {
            s.service(name);
        }
        
    }
    class Service2 
    {
        public synchronized void service(char name)
        {
            for (int i = 3; i >0; i--) {
                System.out.println(i);
            }
        }
    }
    class Service3 extends Service2 
    {
        public void service(char name)
        {
            for (int i = 5; i >0; i--) {
                System.out.println("线程"+name+":"+i);
            }
        }
    }
    线程1:5 线程2:5
  • 若是父类的方法是同步的,若是子类重载同步方法,可是没有synchronized关键字,那么是没有同步做用的。

总结

  • 重入锁,一个得到的锁的线程没执行完能够继续得到锁。
  • 线程占用锁的时候,若是执行的同步出现异常,会将锁让出。
  • 父类方法同步,子类重写该方法(没有synchronized关键字修饰),是没有同步做用的。

同步代码块

public class T1 {
    public static void main(String[] args) {
    Service2 s=new Service2();
        MyThread t1=new MyThread(s,'A');
        MyThread t2=new MyThread(s,'B');
        t1.start();
        t2.start();
        
    }
        
    }
    class Service2 
    {
        public  void service(char name)
        {
            synchronized(this)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
    }
    class MyThread extends Thread
    {
        Service2 s=new Service2();
        char name;
        public  MyThread(Service2 s,char name)
        {
            this.s=s;
            this.name=name;
        }
        public void run()
        {
            s.service(name);
        }
    }
    A:3
    A:2
    A:1
    B:3
    B:2
    B:1
  • 当多个线程访问同一个对象的synchronized(this)代码块时,一段时间内只有一个线程能执行

同步代码块的锁对象

class Service2 
    {
        String s=new String("锁");
        public  void service(char name)
        {
            
            synchronized(s)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
        
    }
  • 将this换成本身建立的锁(一个对象),一样能够实现同步功能

部分同步,部分异步

public  void service(char name)
        {
            for (int i = 6; i >3; i--) {
                System.out.println(name+":"+i);
            }
            synchronized(this)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
        A:6
B:6
A:5
B:5
A:4
B:4
A:3
A:2
A:1
B:3
B:2
B:1
  • 不在同步代码块中的代码能够异步执行,在同步代码块中的代码同步执行

不一样方法里面的synchronized代码块同步执行

public class T1 {
    public static void main(String[] args) {
    Service2 s=new Service2();
        MyThread t1=new MyThread(s,'A');
        MyThread2 t2=new MyThread2(s,'B');
        t1.start();
        t2.start();
        
    }
        
    }
    class Service2 
    {
        public  void service(char name)
        {
            
            synchronized(this)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
        public void service2(char name)
        {
            synchronized(this)  {
                for (int i = 6; i >3; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
    }
    class MyThread extends Thread
    {
        Service2 s=new Service2();
        char name;
        public  MyThread(Service2 s,char name)
        {
            this.s=s;
            this.name=name;
        }
        public void run()
        {
            s.service(name);
        }
    }
    class MyThread2 extends Thread
    {
        Service2 s=new Service2();
        char name;
        public  MyThread2(Service2 s,char name)
        {
            this.s=s;
            this.name=name;
        }
        public void run()
        {
            s.service2(name);
        }
    }
A:3
A:2
A:1
B:6
B:5
B:4
  • 两个线程访问同一个对象的两个同步代码块,这两个代码块是同步执行的

锁不一样没有互斥做用

class Service2 
    {
        Strign s=new String();
        public  void service(char name)
        {
            
            synchronized(s)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
        public void service2(char name)
        {
            synchronized(this)  {
                for (int i = 6; i >3; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
    }
  • 将this改为s,也就是改变锁对象,发现两个方法并非同步执行的

synchronized方法和synchronized(this)代码块是锁定当前对象的

public  void service(char name)
        {
            
            synchronized(this)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
        }
public synchronized void service2(char name)
        {
                for (int i = 6; i >3; i--) {
                    System.out.println(name+":"+i);
                }
        }
  • 将service2的代码块改为synchronized 方法,发现输出结果是同步的的,说明锁定的都是当前对象

总结

  • 同步代码块的锁对象能够是本对象,也能够是其余对象。同一个锁对象能够产生互斥做用,不一样锁对象不能产生互斥做用
  • 一个方法中有同步代码块和非同步代码块,同步代码块的代码是同步执行的(块里的代码一次执行完),而非同步代码块的代码能够异步执行
  • 一个对象中的不一样同步代码块是互斥的,执行完一个代码块再执行另外一个代码块
  • 同步代码块(this)和synchronized方法的锁定的都是当前对象 this

syncronized static 同步静态方法

class Service2 
    {
        
        public  synchronized static void service()
        {
            
            
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            
        }
        
    }
  • 在这里锁对象就不是service对象,而是Class(Class(和String Integer同样)是一个类)

Class锁

class Service2 
    {
        
        public   static void service()
        {
            
            
                synchronized(Service.class)
                {
                for (int i = 3; i >0; i--) {
                    System.out.println(name+":"+i);
                }
            }
            
        }
        
    }
  • 这里的效果和上面静态的synchronized同样

静态类中非静态同步方法

public class T1 {
    public static void main(String[] args) {

        Service.Service2 s=new Service.Service2();
        Thread t1=new Thread(new Runnable()
                {public void run(){s.service();}});
        Thread t2=new Thread(new Runnable()
        {public void run(){Service.Service2.service2();}});
        t1.start();
        
        t2.start();
    }
    
    
    
    }
    class Service{
    static class Service2 
    {
        
        public synchronized   void service()
        {
            

                for (int i = 20; i >10; i--) {
                    
                    System.out.println(i);
                }
            
        }
        public static synchronized void service2()
        {
            for (int i = 9; i >3; i--) {
                System.out.println(i);
            }
        }
        
    }}
    //不一样步执行
  • 这里service方法的锁仍是service对象,

总结

  • Class类也能够是锁,Class锁的实现能够经过静态的synchronizd方法,也能够经过静态方法里面的同步代码块(锁对象为Class)
  • 静态类的同步方法锁对象仍是该类的一个实例

死锁

public class DealThread implements Runnable {

    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代码顺序执行了");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代码顺序执行了");
                }
            }
        }
    }

}
  • 线程AB开始执行时,由于锁不一样,因此不互斥,A当执行到另外一个同步代码块(锁2)的时候,因为这个时候锁给线程B占有了,因此只能等待,一样B线程也是如此,AB互相抢对方的锁,可是因此形成了死锁。

锁对象发生改变

  • 修改锁对象的属性不印象结果,好比此时锁对象为user对象,我把user的name设为jiajun,此时不影响结果

volatile

public class Tee {
    public static void main(String[] args) {
    
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        }
    }

    
    
    class RunThread extends Thread {

         private boolean isRunning = true;

        public boolean isRunning() {
            return isRunning;
        }

        public void setRunning(boolean isRunning) {
            this.isRunning = isRunning;
        }

        @Override
        public void run() {
            System.out.println("进入run了");
            while (isRunning == true) {
            }
            System.out.println("线程被中止了!");
        }

    }
  • 在这里,把vm运行参数设置为-server(右键运行配置,自变量那里能够设置,server参数能够提升运行性能),结果发现虽然咱们将值设置为false,可是却仍然进入死循环。
  • isRunning变量存放在公共堆栈和线程的私有堆栈中,咱们对他赋值为false时,只对公共堆栈进行更新,而但咱们设置为-server后,读取的是线程私有栈的内容,因此也就形成了死循环。咱们能够在isRunning变量前加上volatite关键字,这个时候访问的是公共堆栈,就不会形成死循环了。
  • 之前咱们使用单线程的时候,这种状况状况不会发生,可是当多个线程进行读写操做的时候就可能爆发出问题,这是由于咱们没有用同步机制来保证他,这是咱们须要注意的一点。
public class Tee {
    public static void main(String[] args) {

        MyThread[] mythreadArray = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            mythreadArray[i] = new MyThread();
        }

        for (int i = 0; i < 100; i++) {
            mythreadArray[i].start();
        }
    }

    
 class MyThread extends Thread {
        volatile public static int count;

         private static void addCount() {
            for (int i = 0; i < 100; i++) {
                count++;
            }
            System.out.println("count=" + count);
        }

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

    }
  • 在这里咱们只count前添加volatile,可是最终结果输出的并非10000,说明并无同步的做用,volatile不处理数据原子性(i++不是原子操做)
  • 咱们将volatile去掉,将addcount方法用synchronized修饰,发现输出了10000,说明了synchronized的同步做用不只保证了对同一个锁线程的互斥,还保证了数据的同步。

总结

  • volitate增长了实例变量在对个线程之间的可见性,保证咱们得到的是变量的最新值。
  • volatile在读上面保持了同步做用,可是在写上面不保持同步
  • synchronized的同步做用不只保证了对同一个锁线程的互斥,还保证了数据的同步

volatile对比synchronized

  • 二者修饰的不一样,volatile修饰的是变量,synchronized修饰的是方法和代码块
  • 二者的功能不一样。volatile保证数据的可见性,synchronized是线程同步执行(间接保证数据可见性,让线程工做内存的变量和公共内存的同步)
  • volatile性能比synchronized性能高

用原子类实现i++同步

class MyThread extends Thread {
        static AtomicInteger count=new AtomicInteger(0);
         private  static void addCount() {
            for (int i = 0; i < 100; i++) {
                count.incrementAndGet();
            }
            System.out.println(count.get());
        }

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

    }
  • 将上面的count++进行用原子类AtomicInteger改变,最后输出了1000

我以为分享是一种精神,分享是个人乐趣所在,不是说我以为我讲得必定是对的,我讲得可能不少是不对的,可是我但愿我讲的东西是我人生的体验和思考,是给不少人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引起本身心里的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

做者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】,但愿可以持续的为你们带来好的技术文章!想跟我一块儿进步么?那就【关注】我吧。java

相关文章
相关标签/搜索