• 第22章 java线程(2)-线程同步


    java线程(2)-线程同步

    本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式

    1.线程不安全问题

    什么叫线程不安全呢
    即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题

    对于前一章例子中,使用接口实现方式时会有重复现象,使用接口方式时我们还没有发现明显的现象,但是这并不代表原来的代码没有问题
    我们发现没有问题,必须要有这个意识:看不到问题,有可能是我们经验太少,或者问题出现的不够明显。
    如果问题不够明显,我们可以使用Thread.sleep()方法,正在运行的线程暂停,此时会执行另外的进程,如此可以更方便的看到问题

    注意:
    在线程的run方法上不能使用throws来声明抛出异常,只能在方法中使用try-catch来处理异常
    原因是:子类覆盖父类的原则,子类不能抛出新的异常。
    在Runnable接口中的run方法,都没有抛出异常,所以子类中也不能对这个方法抛出异常
    父类中:public abstract void run();

    解决方法的原理
    回到我们用继承方法的问题上来,如何才能让一个线程没有执行完时另一个线程不会操作一些动作呢。
    解决方案是:保证某一些的动作的同步完成。即保证打印苹果和苹果总数-1操作,必须同步,作为一个单元来执行
    具体的解决方法有3个:
    方式1:同步代码块
    方式2:同步方法
    方式3:锁机制(lock)
    下面分别实现三种方法

    2.线程同步

    思想是把不能中断或者分开执行的代码绑定在一起执行,使其不能在执行中中断

    2.1.同步代码块

    语法:

    synchronized(同步锁)
    {
    	需要同步操作的代码
    }
    

    同步锁:
    为了保证没一个线程都能正常执行原子操作,java引入了线程同步机制
    同步锁又被称为:同步监听对象/同步监听器/互斥锁
    其实就是相当与一把只有一个坑位的卫生间门上的锁,当里面有人的时候,门必须要被锁上,等人出来了再把锁打开
    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁
    java程序运行使用任何对象作为同步锁,但是一般的,我们是用当前并发访问的共同资源作为同步监听对象
    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程,只能在外面等着
    示例代码:

    //接口实现方式
    class Apple1 implements Runnable{
        private int num = 50;
    
        public void run(){
            for (int i = 0; i < 50; i++) {
                synchronized (this){
                    if (num > 0){
                        System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        num--;
                    }
                }
    
            }
        }
    }
    
    
    public class appleImplements {
        public static void main(String[] args) {
            //创建桑个线程
            Apple1 a = new Apple1();
            new Thread(a,"小A").start();
            new Thread(a,"小B").start();
            new Thread(a,"小C").start();
        }
    }
    
    

    注意:因为继承方法没有不能实现资源的共享,所以这里的例子都是以接口方式实现的。

    2.2.同步方法

    同步方法:使用synchronized修饰的方法,就叫做同步方法。保证A线程执行该方法的时候,其他线程只能在外面等着
    语法:

    synchronized public void doWork(){
    	//TODO 要执行的动作
    }
    
    

    同步锁是谁:
    对于非static方法,同步锁就是this
    对于static方法,我们使用当前方法所在的类的字节码对象(Apple2.class)

    注意:不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行
    代码实现:

    
    //接口实现方式
    class Apple2 implements Runnable{
        private int num = 500;
    
        public void run(){
            for (int i = 0; i < 500; i++) {
                eat();
    
            }
        }
    
        synchronized private void eat(){
            if (num > 0){
                System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
                try {
                    Thread.sleep(10);//模拟网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
            }
        }
    }
    
    public class appleImplements {
        public static void main(String[] args) {
            //创建桑个线程
            Apple2 a = new Apple2();
            new Thread(a,"小A").start();
            new Thread(a,"小B").start();
            new Thread(a,"小C").start();
        }
    }
    

    2.3.同步锁(Lock)

    Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象
    语法:

    Lock.png
    思想就是,在执行要绑定的代码之前,上一把锁,执行完之后呢,就把锁打开,这样别的线程就能继续了。
    在使用这个锁之前呢,要先new一个锁出来
    代码示例;

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    
    //接口实现方式
    class Apple4 implements Runnable{
        private int num = 50;
        private final Lock lock = new ReentrantLock();//创建一个锁对象
        public void run(){
            for (int i = 0; i < 50; i++) {
                eat();
    
            }
        }
    
         private void eat(){
            //进入方法:立马加锁
            lock.lock();//获取锁
            if (num > 0){
                try {
                    System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
                    Thread.sleep(100);//模拟网络延迟
                    num--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //执行完,走人,并把锁打开
                    lock.unlock();//释放锁
                }
            }
        }
    }
    
    public class appleImplements {
        public static void main(String[] args) {
            //创建桑个线程
            Apple4 a = new Apple4();
            new Thread(a,"小A").start();
            new Thread(a,"小B").start();
            new Thread(a,"小C").start();
        }
    }
    
    

    2.4.synchronized的好与坏

    好处:保证了多线程并并发访问时的同步操作,避免线程的安全性问题
    缺点:使用synchronized方法/代码块的性能比不用要低一些
    建议:尽量减小synchronized的作用域

  • 相关阅读:
    hdu6148 Valley Numer
    NOI2007 生成树计数
    bzoj3336 Uva10572 Black and White
    hdu1693 eat the trees
    【模板】插头dp
    bzoj4712 洪水
    ZJOI2010 基站选址
    poj2376 Cleaning Shifts
    bzoj4367 [IOI2014]holiday假期
    bzoj4951 [Wf2017]Money for Nothing
  • 原文地址:https://www.cnblogs.com/cenyu/p/6149844.html
Copyright © 2020-2023  润新知