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.

 

  

原文地址:https://www.cnblogs.com/trekxu/p/8970948.html