zoukankan      html  css  js  c++  java
  • 008 同步

    一 概述

    多线程在效率上能带给我们一些提升,但是也带来了一些其它的问题,这些问题如果不解决,我们根本无法保证线程的运算结果是正确的.

    那么,这个时候使用多线程根本不存在任何意义.

      带来的问题:

        [1] 多线程共享一个资源---造成资源状态不一致

        [2] 多线程的执行顺序 ,线程一旦运行起来,我们能无法控制线程到底是怎么运行的.

        对此,我们就需要使用线程的同步和线程的通信来完成上面问题的解决.


    二 . 线程间的资源共享

    当多个线程之间共享资源的时候,就可能出现资源使用上的问题.

      这里需要指出的是可能会出现问题,不是一定会出现问题.

      我们现在所要做的第一步就是明白什么样的情况下会出现问题.

        问题的解决就是加锁,   

            如果在根本没有资源共享出现问题的地方加上锁,除了影响效率之外根本没有任何的好处.


    三 .资源共享何时出现问题

      问题1 : 当多个线程都读取一个资源问题,但是不允许对资源问题进行任何修改.

            此时根本不需要进行加锁操作,因为无论何时,资源的状态都是一致性的.

      问题2 : 当多个线程使用一个资源问题,可以对其进行修改操作的时候.

          这个时候,我们就需要进行加锁.

            加锁的原因只有一个: 保证资源的一致性状态.

      

    上面的一致性比较难理解:

      一致性就是说,资源的状态一定需要是一个稳定状态.当一个线程对资源进行修改的时候,资源的状态在修改过程中就不是稳定,那么其他线程就不应该看到这种状态.

     


    四 .测试代码

    我们选用一个非常经典的例子:

      

    public class Ticket {
        //表示100张票
        private static int  num = 100;
        
        public static void consume() {
            for(;;) {
                if(num > 0) {
                    System.out.println(Thread.currentThread().getName()+": 卖出第"+(num--)+"张票");
                }else
                    return ;
            }
        }
        
        public static void main(String[] args) {
            new Thread("thread1") {
                @Override
                public void run() {
                    consume();
                }
            }.start();
            new Thread("thread2") {
                @Override
                public void run() {
                    consume();
                }
            }.start();
        }
    }

    在上面的例子中,我们开启两个线程再卖票.我们的共享数据就是num这个参数.

    那上面的例子有问题没有呢?

    我们稍加改造一下:

    public static void consume() {
            for(;;) {
                if(num > 0) {
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName()+": 卖出第"+(num--)+"张票");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else
                    return ;
            }
        }

    仅仅是在consume方法之中,在打印之前让线程休眠一下.

    我们可以看到结果:

      

    现在出现了0张票的概念,也就是说我们上面的例子中存在并发的问题.


    五 .问题的解决

      由于资源共享问题而出现的并发问题,我们使用加锁就能完成.

      在java之中,加锁的方式有很多.现在我们只说最为简单的几种,在后面我们会说并法宝为我们提供的一系列的锁机制.

    [1] 方式1 : 同步代码块

      我们可以使用synchronized 关键词来完成. 

    public static void consume() {
            for(;;) {
                synchronized (Ticket.class) {
                    if (num > 0) {
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + ": 卖出第" + (num--) + "张票");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else
                        return;
                }
            }
        }

    我们将出现问题的代码加入到同步代码块之中,那么着一段代码就仅仅允许获取到锁的线程运行,也就是说不会发生资源同时被多个线程争夺的问题了.

      注意 : 我们使用的锁必须说唯一的锁,否则还是会出现问题的.

    [2]方式二 . 使用同步方法 

    public synchronized static void consume() {
            for(;;) {
                    if (num > 0) {
                        try {
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName() + ": 卖出第" + (num--) + "张票");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else
                        return;
                }
        }

    在这里我们使用的是同步方法,其实和同步方法一致,也是对整个方法进行加锁,这个方法仅仅允许一个线程访问. 


    六 .同步方法和同步代码块

      其实本质上都是一样的东西,

      我们如何执行上面的同步方法的时候,我们会发现我们的并发只有一个线程在工作.

      为什么呢 ? 原因就是一个线程一旦抢占了锁之后,就把所有的票卖没了 .

    现在我们换一个方式:  

    public class Ticket {
        // 表示100张票
        private static int num = 100;
    
        public synchronized static void consume() {
            if (num > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + ": 卖出第" + (num--) + "张票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } 
        }
    
        public static void main(String[] args) {
            new Thread("thread1") {
                @Override
                public void run() {
                    for(;;)
                    consume();
                }
            }.start();
            new Thread("thread2") {
                @Override
                public void run() {
                    for(;;)
                    consume();
                }
            }.start();
        }
    }

    我们现在的线程逻辑单元仅仅是卖一张票了.

    那么我们又可以看到线程的交替运行了.

    总结 :

      [1]静态同步方法的锁是class

      [2]普通方法加锁的对象是this.

     

      

  • 相关阅读:
    python 查询文件存放地址
    类数组对象:arguments
    前端开发工程师从入门到精通学习过程及资源总结(持续更新中)
    CSS3的圆角border-radius属性
    内联元素inline-block空隙问题
    js中typeof用法详细介绍
    正则表达式
    sublime text 3 快捷键大全以及配置编译环境
    c# 泛型类
    c# 中config.exe 引发的一些问题
  • 原文地址:https://www.cnblogs.com/trekxu/p/8970948.html
Copyright © 2011-2022 走看看