• java-多线程安全-锁


    一 同步函数

    1.1 一般的方法

    同步的另一种体现形式:同步函数

    同步函数使用的锁是哪个?
    经过分析:大概猜的是this,因为函数必须被对象调用。

    验证:
    写一个同步代码块,写一个同步函数,如果同步代码块中的锁对象和同步函数中的锁对象是同一个,
    就同步了,就没有错误的数据了。如果不是同一个锁对象,就不同步出现错误数据。

    让两个线程,一个线程在同步代码块中执行,一个线程在同步函数中执行。

    总结:同步函数使用的锁时this。

    同步函数和同步代码块有什么区别吗?

    同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数。
    同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。
    同步代码块较为常用。

    package test;
    
    class Ticket implements Runnable {
        private int tickets = 100;
        private Object obj = new Object();
        boolean flag = true;
    
        public void run() {
                while (true) {
                    sale();
                    
                    }
            
        }
    
        public synchronized void sale()// 同步函数,使用的锁对象 this    {
            if (tickets > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + "...sale..." + tickets--);// 打印线程名称。
            }
        }
    }
    
    class ThreadDemo4 {
        public static void main(String[] args) {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
    
            t1.start();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            // 切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
            t.flag = false;
    
            t2.start();
        }
    }

    1.2 静态方法

    static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class 

    class Ticket implements Runnable
    {
        private static int tickets = 100;
        private Object obj = new Object();
        boolean flag = true;
        public void run()
        {
            if(flag){
                while(true){
                    synchronized(Ticket.class){
                        if(tickets>0){
                            try{Thread.sleep(10);}catch(InterruptedException e){}
                            System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。
                        }
                    }
                }
            }
            else{
                while(true){
                    this.sale();
                }
            }
        }
    
        public static synchronized void sale()//
        {
            if(tickets>0)
            {
                try{Thread.sleep(10);}catch(InterruptedException e){}
                System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
            }
        }
    }
    class ThreadDemo5 
    {
        public static void main(String[] args) 
        {
            Ticket t = new Ticket();
    
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
    
            t1.start();
            try{Thread.sleep(10);}catch(InterruptedException e){}
            //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
            t.flag = false;
    
            t2.start();
        }
    }

    二 单例的安全问题

    //饿汉式。  多线程并发饿汉式没问题。
    class Single
    {
        private static final Single s = new Single();
    
        private Single(){}
    
        public static Single getInstance()
        {
            return s;
        }
    }
    
    //懒汉式。
    class Single
    {
        private static  Single s = null;
    
        private Single(){}
    
        /*
        并发访问会有安全隐患,所以加入同步机制解决安全问题。
        但是,同步的出现降低了效率。
        可以通过双重判断的方式,解决效率问题,减少判断锁的次数。
    重在分析
    */ public static Single getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) // -->0 -->1 s = new Single(); } } return s; } } class Demo implements Runnable { public void run() { Single.getInstance(); } } class ThreadDemo6 { public static void main(String[] args) { System.out.println("Hello World!"); } }

     三 死锁

    同步的另一个弊端:

    情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
    这时容易引发一种现象:死锁。
    这种情况能避免就避免掉

    例:一碗饭.一个人拿一个筷子,谁也不放.

    //Thread-0
    synchronized(obj1)
    {
    -->thread-0 obj1
    synchronized(obj2)
    {
    
    }
    
    }
    //Thread-1
    synchronized(obj2)
    {
    Thread-1 obj2
    synchronized(obj1)
    {
    
    }
    
    }

     例如

    class Ticket implements Runnable
    {
    private int tickets = 100;
    private Object obj = new Object();
    boolean flag = true;
    public void run()
    {
    if(flag){
    while(true){
    synchronized(obj){
    sale();//this lock;
    }
    }
    }
    else{
    while(true){
    this.sale();
    }
    }
    }
    
    public synchronized void sale()//this lock
    {
    synchronized(obj)//obj lock
    {
    if(tickets>0)
    {
    try{Thread.sleep(10);}catch(InterruptedException e){}
    System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
    }
    }
    }
    }
    class ThreadDemo7 
    {
    public static void main(String[] args) 
    {
    Ticket t = new Ticket();
    
    Thread t1 = new Thread(t);
    Thread t2 = new Thread(t);
    
    t1.start();
    try{Thread.sleep(10);}catch(InterruptedException e){}
    //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
    t.flag = false;
    
    t2.start();
    }
    }

     又例如

    class Test implements Runnable
    {
        private boolean flag;
        Test(boolean flag)
        {
            this.flag = flag;
        }
    
        public void run()
        {
            if(flag)
            {
                while(true)
                {
                    synchronized(MyLock.LOCKA)
                    {
                        System.out.println(Thread.currentThread().getName()+"...if......locka");
                        synchronized(MyLock.LOCKB)
                        {
                            System.out.println(Thread.currentThread().getName()+"...if......lockb");
                        }
                    }
                }
            }
            else
            {
                while(true)
                {
                    synchronized(MyLock.LOCKB)
                    {
                        System.out.println(Thread.currentThread().getName()+"...else......lockb");
                        synchronized(MyLock.LOCKA)
                        {
                            System.out.println(Thread.currentThread().getName()+"...else......locka");
                        }
                    }
                }
            }
        }
    }
    //定义一个用于存储锁对象类。
    class MyLock
    {
        public static final Object LOCKA = new Object();
        public static final Object LOCKB = new Object();
    }
    
    class DeadLockTest 
    {
        public static void main(String[] args) 
        {
            //创建两个线程任务。
            Test t1 = new Test(true);
            Test t2 = new Test(false);
            
            Thread t11 = new Thread(t1);
            Thread t22 = new Thread(t2);
            t11.start();
            t22.start();
    
        }
    }

    四 生产者和消费者

    多线程中最为常见的应用案例:
    生产者消费者问题。
    生产和消费同时执行,需要多线程。
    但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。

    1,描述一下资源
    2,描述生产者,因为具备着自己的任务
    3,描述消费者,因为具备着自己的任务

    问题1:数据错误:已经被生产很早期的商品,才被消费到。
    出现线程安全问题,加入了同步解决。使用同步函数。
    问题已解决:不会在消费到之前很早期的商品。

    问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
    希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。
    搞清楚几个问题?
    生产者什么时候生产呢?消费者什么时候应该消费呢?
    当盘子中没有面包时,就生产,如果有了面包,就不要生产。
    当盘子中已有面包时,就消费,如果没有面包,就不要消费。

    //1,描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
    class Resource
    {
        private String name;
        private int count = 1;
        
        //1,提供设置的方法。
        publicvoid set(String name)
        {
            //给成员变量赋值并加上编号。
            this.name = name + count;
            //编号自增。
            count++;
            //打印生产了哪个商品。
            System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);
        }
        publicvoid out()
        {
            System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
        }
    }
    
    //2,描述生产者。
    class Producer implements Runnable
    {
        private Resource r ;
        // 生产者一初始化就要有资源,需要将资源传递到构造函数中。
        Producer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.set("面包");
            }
        }
    }
    
    //3,描述消费者。
    class Consumer implements Runnable
    {
        private Resource r ;
        // 消费者一初始化就要有资源,需要将资源传递到构造函数中。
        Consumer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.out();
            }
        }
    }
    
    class ThreadDemo8
    {
        public static void main(String[] args) 
        {
            //1,创建资源对象。
            Resource r = new Resource();
    
            //2,创建线程任务。
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //3,创建线程。
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(con);
    
            t1.start();
            t2.start();
        }
    }

    问题1: 已经被生产很早期的商品,才被消费到。
    出现线程安全问题,加入了同步解决。使用同步函数。
    问题已解决:不会在消费到之前很早期的商品。

    class Resource
    {
        private String name;
        private int count = 1;
        
        //1,提供设置的方法。
        public synchronized void set(String name)
        {
            //给成员变量赋值并加上编号。
            this.name = name + count;
            //编号自增。
            count++;
            //打印生产了哪个商品。
            System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);
        }
        public synchronized void out()
        {
            System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
        }
    }

    问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
    希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。
    搞清楚几个问题?

    生产者什么时候生产呢?消费者什么时候应该消费呢?
    当盘子中没有面包时,就生产,如果有了面包,就不要生产。
    当盘子中已有面包时,就消费,如果没有面包,就不要消费。

    生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。
    消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。

    等待:wait();
    告诉:notify();//唤醒

    问题解决:实现生产一个消费一个。

    4.1 等待/唤醒机制

    wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
    notify():会唤醒线程池中任意一个等待的线程。
    notifyAll():会唤醒线程池中所有的等待线程。

    记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。
    同一个锁上的notify,只能唤醒该锁上的被wait的线程。

    例:等待吃饭

    还有银行 

    为什么这些方法定义在Object类中呢?
    因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。

    举例:小朋友抓人游戏

    /1,描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
    class Resource
    {
        private String name;
        private int count = 1;
    
        //定义标记。
        private boolean flag = false;
        
        //1,提供设置的方法。
        public synchronized void set(String name)
        {
    
            if(flag)
                try{this.wait();}catch(InterruptedException e){}
            //给成员变量赋值并加上编号。
            this.name = name + count;
            //编号自增。
            count++;
            //打印生产了哪个商品。
            System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);
    
            //将标记改为true。
            flag = true;
            //唤醒消费者。
            this.notify();
        }
        public synchronized void out()
        {
            if(!flag)
                try{this.wait();}catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
            //将标记该为false。
            flag = false;
            //唤醒生产者。
            this.notify();
        }
    }
    
    //2,描述生产者。
    class Producer implements Runnable
    {
        private Resource r ;
        // 生产者一初始化就要有资源,需要将资源传递到构造函数中。
        Producer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.set("面包");
            }
        }
    }
    
    //3,描述消费者。
    class Consumer implements Runnable
    {
        private Resource r ;
        // 消费者一初始化就要有资源,需要将资源传递到构造函数中。
        Consumer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.out();
            }
        }
    }
    
    class ThreadDemo9
    {
        public static void main(String[] args) 
        {
            //1,创建资源对象。
            Resource r = new Resource();
    
            //2,创建线程任务。
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //3,创建线程。
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(con);
    
            t1.start();
            t2.start();
        }
    }

     五 多生产和多消费

     加入多生产多消费

    public static void main(String[] args) 
        {
            //1,创建资源对象。
            Resource r = new Resource();
    
            //2,创建线程任务。
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //3,创建线程。
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }

    问题1 生产了商品没有被消费,同一个商品被消费多次。
    Thread-2......生产者....面包40527//没有被消费。
    Thread-3......消费者....面包40528
    Thread-2....消费者....面包40528

    分析过程

     

     

    被唤醒的线程没有判断标记,造成问题1的产生
    解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。记住:多生产多消费,必须时while判断条件

    class Resource
    {
        private String name;
        private int count = 1;
    
        //定义标记。
        private boolean flag = false;
        
        //1,提供设置的方法。
        public synchronized void set(String name)//   
        {
    
            while(flag)    //////////
                try{this.wait();}catch(InterruptedException e){}// t1等  t2等
            //给成员变量赋值并加上编号。
            this.name = name + count;//商品1  商品2  商品3
            //编号自增。
            count++;//2 3  4
            //打印生产了哪个商品。
            System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1  生产商品2  生产商品3
    
            //将标记改为true。
            flag = true;
            //唤醒消费者。
            this.notify();
        }
        public synchronized void out()// 
        {
            while(!flag)//////////
                try{this.wait();}catch(InterruptedException e){}//t3等  //t4等
            System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1
            //将标记该为false。
            flag = false;
            //唤醒生产者。
            this.notify();
        }
    }
    
    //2,描述生产者。
    class Producer implements Runnable
    {
        private Resource r ;
        // 生产者一初始化就要有资源,需要将资源传递到构造函数中。
        Producer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.set("面包");
            }
        }
    }
    
    //3,描述消费者。
    class Consumer implements Runnable
    {
        private Resource r ;
        // 消费者一初始化就要有资源,需要将资源传递到构造函数中。
        Consumer(Resource r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.out();
            }
        }
    }
    
    
    
    
    class ThreadDemo10
    {
        public static void main(String[] args) 
        {
            //1,创建资源对象。
            Resource r = new Resource();
    
            //2,创建线程任务。
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //3,创建线程。
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

     

    问题2:发现while判断后,死锁了。
    原因:生产方唤醒了线程池中生产方的线程。本方唤醒了本方。
    解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。

     

     六 Condition

    Condition/*
    jdk1.5以后提供多生产多消费的解决方案。

    在java.util.concurrent.locks 软件包中提供相应的解决方案
    Lock接口:比同步更厉害,有更多操作。lock():获取锁 unlock():释放锁;
    提供了一个更加面对对象的锁,在该锁中提供了更多的显示的锁操作。
    替代同步。

    升级到JDK1.5,先把同步改成 Lock。

    已经将旧锁替换成新锁,那么锁上的监视器方法(wait,notify,notifyAll)也应该替换成新锁的监视器方法。
    而jdk1.5中将这些原有的监视器方法封装到了一个Condition对象中。
    想要获取监视器方法,需要先获取Condition对象。

    Condition对象的出现其实就是替代了Object中的监视器方法。
    await();
    signal();
    signalAll();

    将所有的监视器方法替换成了Condition。
    功能和ThreadDemo10.java老程序的功能一样,仅仅是用新的对象。改了写法而已。
    但是问题依旧;效率还是低。

    希望本方可以唤醒对方中的一个。
    老程序中可以通过两个锁嵌套完成,但是容易引发死锁。

    新程序中,就可以解决这个问题,只用一个锁,
    可以在一个锁上加上多个监视器对象。


    */

    import java.util.concurrent.locks.*;
    
    class Resource
    {
    private String name;
    private int count = 1;
    
    //定义一个锁对象。
    private final Lock lock = new ReentrantLock();
    //获取锁上的Condition对象。为了解决本方唤醒对方的问题。可以一个锁创建两个监视器对象。
    
    private Condition produce = lock.newCondition();//负责生产。
    private Condition consume = lock.newCondition();//负责消费。
    
    //定义标记。
    private boolean flag = false;
    
    //1,提供设置的方法。
    public void set(String name)// 
    {
    //获取锁。
    lock.lock();
    try{
    
    while(flag)
    try{produce.await();}catch(InterruptedException e){}// t1等 t2等
    this.name = name + count;//商品1 商品2 商品3
    count++;//2 3 4
    System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1 生产商品2 生产商品3
    
    //将标记改为true。
    flag = true;
    //执行的消费者的唤醒。唤醒一个消费者就哦了。
    consume.signal();
    }finally{
    
    lock.unlock();//一定要执行。
    }
    }
    public void out()// 
    {
    
    lock.lock();
    try{
    while(!flag)
    try{consume.await();}catch(InterruptedException e){}//t3等 //t4等
    System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1
    //将标记该为false。
    flag = false;
    //
    produce.signal();
    }
    finally{
    lock.unlock();
    }
    }
    }
    
    //2,描述生产者。
    class Producer implements Runnable
    {
    private Resource r ;
    // 生产者一初始化就要有资源,需要将资源传递到构造函数中。
    Producer(Resource r)
    {
    this.r = r;
    }
    public void run()
    {
    while(true)
    {
    r.set("面包");
    }
    }
    }
    
    //3,描述消费者。
    class Consumer implements Runnable
    {
    private Resource r ;
    // 消费者一初始化就要有资源,需要将资源传递到构造函数中。
    Consumer(Resource r)
    {
    this.r = r;
    }
    public void run()
    {
    while(true)
    {
    r.out();
    }
    }
    }
    
     
    
    
    class ThreadDemo11
    {
    public static void main(String[] args) 
    {
    //1,创建资源对象。
    Resource r = new Resource();
    
    //2,创建线程任务。
    Producer pro = new Producer(r);
    Consumer con = new Consumer(r);
    
    //3,创建线程。
    Thread t1 = new Thread(pro);
    Thread t2 = new Thread(pro);
    Thread t3 = new Thread(con);
    Thread t4 = new Thread(con);
    
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    }
    }


    作者:8亩田
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.

    本文如对您有帮助,还请多帮 【推荐】 下此文。
    如果喜欢我的文章,请关注我的公众号
    如果有疑问,请下面留言

    学而不思则罔 思而不学则殆
  • 相关阅读:
    程序猿也爱学英语(上),有图有真相
    时间&物质&效率
    20130722
    Java数组操作工具
    小学课文《挑山工》
    字符编解码的故事(ASCII,ANSI,Unicode,Utf-8区别)
    String[]转化暴露“思维误区”
    2017.9.17 小测试小整理
    Noip2016 提高组 Day1
    luogu P2585 [ZJOI2006]三色二叉树
  • 原文地址:https://www.cnblogs.com/liu-wang/p/8284664.html
Copyright © 2020-2023  润新知