Java并发编程学习(二)—— 对象及变量的并发访问

一、synchronized

1、synchronized取得的都是对象锁,而不是把一段代码或方法当做锁。

package com.amuro.studythread.chapter_2_concurrent_access;

public class SynchronizedBase
{
    public static void main(String[] args)
    {
        SynchronizedObject obj = new SynchronizedObject();
        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                obj.test1();
            }
        });
        t1.setName("t1");
        t1.start();

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                obj.test2();
            }
        });
        t2.setName("t2");
        t2.start();
    }

    private static class SynchronizedObject
    {
        synchronized void test1()
        {
            System.out.println("test1 begin with thread " + Thread.currentThread().getName());
            try
            {
                Thread.sleep(5000);
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("test1 end with thread " + Thread.currentThread().getName());
        }

        synchronized void test2()
        {
            System.out.println("test2 begin with thread " + Thread.currentThread().getName());
            try
            {
                Thread.sleep(1000);
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("test2 end with thread " + Thread.currentThread().getName());
        }
    }
}

这个例子中,虽然两个线程访问的是同一个对象不同的方法,但因为两个方法都加了对象锁,所以必须等到先拿到锁的那个线程执行完成释放锁后,才能执行自己的方法。

2、脏读

package com.amuro.studythread.chapter_2_concurrent_access;

public class DirtyRead
{
    public static void main(String[] args)
    {
        SynchronizedObject obj = new SynchronizedObject();
        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                obj.set("b", "bb");
            }
        });
        t1.start();

        try
        {
            Thread.sleep(1000);
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                System.out.println(obj.getPassword());
            }
        });
        t2.start();
    }

    private static class SynchronizedObject
    {
        private String username = "a";
        private String password = "aa";

        synchronized void set(String username, String password)
        {
            this.username = username;
            try
            {
                Thread.sleep(5000);
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            this.password = password;
        }

        public String getPassword()
        {
            return password;
        }
    }
}

打印结果是aa,也就是说线程1还没有来得及修改好所有变量的值,线程2已经去获取值了,赋值同步而取值不同步导致的数据错误就叫脏读,该实例的解决方法就是在getPassword方法前也加上synchronized关键字。

3、可重入锁:自己可以再次获取自己的内部锁。比如某个线程获得了某个对象的锁,此时这个对象锁还没有释放,当它再次要获得这个对象的锁时还是可以获取的。简单来说:

synchronized test1()
{
    System.out.println("1");
    test2();
}

synchronized test2()
{
    System.out.printlin("2");
}

如果线程1拿到了对象锁,那方法1是可以顺利调用方法2的。所以synchronized是可重入锁。
PS:可重入锁同时适用于父类和子类。

4、当一个线程执行的代码出现异常(未捕获)时,其所持有的锁会自动释放。

5、synchronized没有继承性。

6、synchronized(this) 用同步代码块解决同步方法的弊端。

package com.amuro.studythread.chapter_2_concurrent_access;

public class SynchronizedCompare
{
    public static void main(String[] args)
    {
        SyncMethod.invoke();
        SyncThis.invoke();
    }

    private static class SyncMethod
    {   
        private static long begin = 0;
        private static long end = 0;

        private static void invoke()
        {
            SyncMethod sm = new SyncMethod();
            Thread t1 = new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    begin = System.currentTimeMillis();
                    sm.test();
                }
            });
            t1.start();

            try
            {
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            Thread t2 = new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    sm.test();
                    end = System.currentTimeMillis();
                    System.out.println("SyncMethod use time " + (end - begin));
                }
            });
            t2.start();
        }

        int i = 0;

        synchronized void test()
        {
            try
            {
                Thread.sleep(3000);
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            i++;

        }

    }

    private static class SyncThis
    {
        private static long begin = 0;
        private static long end = 0;

        private static void invoke()
        {
            SyncThis sm = new SyncThis();
            Thread t1 = new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    begin = System.currentTimeMillis();
                    sm.test();
                }
            });
            t1.start();

            try
            {
                Thread.sleep(100);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            Thread t2 = new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    sm.test();
                    end = System.currentTimeMillis();
                    System.out.println("SyncThis use time " + (end - begin));
                }
            });
            t2.start();
        }

        int i = 0;

        void test()
        {
            try
            {
                Thread.sleep(3000);
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            synchronized (this)
            {
                i++;
            }

        }

    }


}

程序输出:
SyncThis use time 3104
SyncMethod use time 6007
假设我们只需要保证对i操作的线程安全,那用syncMethod的时间消耗就比Sync块的消耗要大一倍,原因就是syncMethod是很蛮横的把类中所有加了synchronized的方法都锁了起来。

7、synchronized(anyObject)
一个类中如果有多个synchronized方法,当一个方法阻塞时会导致该类中所有的同步方法都阻塞,所以使用非this锁可以大大提高效率。

8、synchronized(anyObject)解决synchronized方法的脏读问题

package com.amuro.studythread.chapter_2_concurrent_access;

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

public class DirtyReadPro
{
    public static void main(String[] args) throws Exception
    {
        MyOneList list = new MyOneList();
        MyThread1 t1 = new MyThread1(list);
        t1.setName("t1");
        t1.start();
        MyThread2 t2 = new MyThread2(list);
        t2.setName("t2");
        t2.start();
        Thread.sleep(6000);
        System.out.println(list.getSize());
    }

    private static class MyOneList
    {
        List<String> list = new ArrayList<>();
        synchronized void add(String data)
        {
            System.out.println(Thread.currentThread() + " add data");
            list.add(data);
        }

        synchronized int getSize()
        {
            System.out.println(Thread.currentThread() + " get Size");
            return list.size();
        }
    }

    private static class Service
    {
        MyOneList addServiceMethod(MyOneList list, String data)
        {
            try
            {
                if(list.getSize() < 1)
                {
                    Thread.sleep(2000);
                    list.add(data);
                }
            }
            catch(Exception e)
            {

            }

            return list;
        }
    }

    public static class MyThread1 extends Thread
    {
        MyOneList list;

        public MyThread1(MyOneList list)
        {
            super();
            this.list = list;
        }

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

    public static class MyThread2 extends Thread
    {
        MyOneList list;

        public MyThread2(MyOneList list)
        {
            super();
            this.list = list;
        }

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


}

程序输出:
Thread[t1,5,main] get Size
Thread[t2,5,main] get Size
Thread[t1,5,main] add data
Thread[t2,5,main] add data
Thread[main,5,main] get Size
2
原因很简单,线程1调用getSize方法后sleep,此时MyOneList的锁已经释放,所以线程2调用getSize方法时直接可以进入,并且此时1的add方法还没来得及执行,导致1和2都可以执行add方法。
修正:

package com.amuro.studythread.chapter_2_concurrent_access;

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

public class DirtyReadProFix
{
    public static void main(String[] args) throws Exception
    {
        MyOneList list = new MyOneList();
        MyThread1 t1 = new MyThread1(list);
        t1.setName("t1");
        t1.start();
        MyThread2 t2 = new MyThread2(list);
        t2.setName("t2");
        t2.start();
        Thread.sleep(6000);
        System.out.println(list.getSize());
    }

    private static class MyOneList
    {
        List<String> list = new ArrayList<>();
        void add(String data)
        {
            System.out.println(Thread.currentThread() + " add data");
            list.add(data);
        }

        int getSize()
        {
            System.out.println(Thread.currentThread() + " get Size");
            return list.size();
        }
    }

    private static class Service
    {
        MyOneList addServiceMethod(MyOneList list, String data)
        {
            try
            {
                synchronized (list)
                {
                    if(list.getSize() < 1)
                    {
                        Thread.sleep(2000);
                        list.add(data);
                    }
                }

            }
            catch(Exception e)
            {

            }

            return list;
        }
    }

    public static class MyThread1 extends Thread
    {
        MyOneList list;

        public MyThread1(MyOneList list)
        {
            super();
            this.list = list;
        }

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

    public static class MyThread2 extends Thread
    {
        MyOneList list;

        public MyThread2(MyOneList list)
        {
            super();
            this.list = list;
        }

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

当把list作为锁时,因为线程1,2操作的都是同一个list对象,所以在线程1执行完add操作前,线程2无法进入。这样就能解决脏读的问题了。

9、synchronized方法用于静态方法时,锁的是当前类的class,所以对当前类的所有实例都有效,因为class是唯一的嘛~

10、常量池特性的类的坑

String a = "A";
String b = "A";

当用a和b做锁的时候,本质是一把锁,因为a == b。

11、经典死锁

package com.amuro.studythread.chapter_2_concurrent_access;

public class DeadLock1
{
    public static void main(String[] args) throws Exception
    {
        DeadLockRunnable runnable = new DeadLockRunnable();
        Thread t1 = new Thread(runnable);
        t1.setName("t1");
        t1.start();

        Thread.sleep(100);

        Thread t2 = new Thread(runnable);
        t2.setName("t2");
        t2.start();
    }

    private static class DeadLockRunnable implements Runnable
    {
        private Object lock1 = new Object();
        private Object lock2 = new Object();

        @Override
        public void run()
        {
            try
            {
                if(Thread.currentThread().getName().equals("t1"))
                {
                    synchronized (lock1)
                    {
                        System.out.println("t1 acquire lock1");

                        Thread.sleep(1000);

                        System.out.println("t1 try to acquire lock2");
                        synchronized (lock2)
                        {
                            System.out.println("t1 end");
                        }
                    }
                }
                else
                {
                    synchronized (lock2)
                    {
                        System.out.println("t2 acquire lock2");
                        Thread.sleep(1000);

                        System.out.println("t2 try to acquire lock1");
                        synchronized (lock1)
                        {
                            System.out.println("t2 end");
                        }

                    }
                }
            }
            catch(Exception e)
            {

            }

        }
    }
}

程序输出:
t1 acquire lock1
t2 acquire lock2
t1 try to acquire lock2
t2 try to acquire lock1
程序永远无法执行完成。线程1拿到锁1后,在等待时锁2已经被线程2获取,当等待结束时线程1尝试再去获取锁2就会阻塞除非锁2被释放,而线程2在等待后又去尝试目前被线程1占用的锁1,两个线程永远也无法获得对方持有的锁,于是就进入了死锁。

12、只要对象不变,修改对象的属性对锁没有影响,也就是说对synchronized来说,仍然是一把锁。

二、volatile

1、JVM被设置成-server模式时为了提升线程运行效率,线程会一直在私有堆栈中取得私有变量的值,此时调用该变量的set方法时,设置的却是该变量在公用堆栈中的值,从而导致设置失败。这个时候就要使用volatile关键字,它的主要作用就是让线程访问该私有变量时,强制从公共堆栈中取值。
不使用:
这里写图片描述

使用后:
这里写图片描述

(怎么感觉像减肥药广告……)

2、volatile提供变量多线程间的可见性,但不具备原子性。

3、实现i++操作时的线程安全,除了使用synchronized,也可以使用原子类(AtomicInteger)。底层都是使用了Java的Unsafe类,这个类可以把C能做Java不能做的事都做了,后面有机会专门写一篇聊聊。

4、synchronized可以同时保证原子性和可见性,它不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值