zoukankan      html  css  js  c++  java
  • 多线程之同步与死锁问题

    一,线程安全问题

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,

    而且其他的变量的值也和预期的是一样的,就是线程安全的。我们来看一个经典的线程安全问题,电影院买票。

    public class ThreadDemo {
        public static void main(String[] args) {
            //创建票对象
            Ticket ticket = new Ticket();
            
            //创建3个窗口
            Thread t1  = new Thread(ticket, "窗口1");
            Thread t2  = new Thread(ticket, "窗口2");
            Thread t3  = new Thread(ticket, "窗口3");
            
            t1.start();
            t2.start();
            t3.start();
        }
    }
    模拟票
    public class Ticket implements Runnable {
        //共100票
        int ticket = 100;
    
        @Override
        public void run() {
            //模拟卖票
            while(true){
                if (ticket > 0) {
                    //模拟选坐的操作
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                }
            }
        }

    运行结果发现:上面程序出现了问题

                              票出现了重复的票

                              错误的票 0、-1

    其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,

    而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,

    否则的话就可能影响线程安全。

    二,线程同步

    java中提供了线程同步机制,它能够解决上述的线程安全问题。

    线程同步的方式有两种:

          方式1:同步代码块

                     方式2:同步方法

    同步代码块: 在代码块声明上 加上synchronized
    synchronized (锁对象) {
        可能会产生线程安全问题的代码
    }
    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
    
    
    同步方法1:在方法声明上加上synchronized
    public synchronized void method(){
           可能会产生线程安全问题的代码
    }
        同步方法中的锁对象是 this
    
    
    静态同步方法: 在方法声明上加上static synchronized
    public static synchronized void method(){
    可能会产生线程安全问题的代码
    }
    静态同步方法中的锁对象是 类名.class

    三,死锁问题

    同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
    synchronzied(A锁){
        synchronized(B锁){
             
    }
    }

    代码演示:

    package 多线程;
    
    public class Demo03 {
    
        public static void main(String[] args) {
            Runn r1 = new Runn();
            Thread t1 = new Thread(r1);
            Thread t2 = new Thread(r1);
            t1.start();
            t2.start();
            for(int i=0;i<20;i++)
            {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        
        
    
    }
    
    class Runn implements Runnable{
    
        @Override
        public void run() {
            for(int i=0;i<20;i++)
            {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        
    }

    四,Lock接口

    Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。

    我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改:

    public class Ticket implements Runnable {
        //共100票
        int ticket = 100;
        
        //创建Lock锁对象
        Lock ck = new ReentrantLock();
        
        @Override
        public void run() {
            //模拟卖票
            while(true){
                //synchronized (lock){
                ck.lock();
                    if (ticket > 0) {
                        //模拟选坐的操作
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                    }
                ck.unlock();
                //}
            }
        }
    }

    五,线程间的通信

    等待唤醒机制所涉及到的方法:

            wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

            notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

            notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

    模拟资源类
    public class Resource {
        private String name;
        private String sex;
        private boolean flag = false;
    
        public synchronized void set(String name, String sex) {
            if (flag)
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            // 设置成员变量
            this.name = name;
            this.sex = sex;
            // 设置之后,Resource中有值,将标记该为 true ,
            flag = true;
            // 唤醒output
            this.notify();
        }
    
        public synchronized void out() {
            if (!flag)
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            // 输出线程将数据输出
            System.out.println("姓名: " + name + ",性别: " + sex);
            // 改变标记,以便输入线程输入数据
            flag = false;
            // 唤醒input,进行数据输入
            this.notify();
        }
    }
    
    输入线程任务类
    public class Input implements Runnable {
        private Resource r;
    
        public Input(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            int count = 0;
            while (true) {
                if (count == 0) {
                    r.set("小明", "男生");
                } else {
                    r.set("小花", "女生");
                }
                // 在两个数据之间进行切换
                count = (count + 1) % 2;
            }
        }
    }
    
    输出线程任务类
    public class Output implements Runnable {
        private Resource r;
    
        public Output(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            while (true) {
                r.out();
            }
        }
    }
    
    测试类
    public class ResourceDemo {
        public static void main(String[] args) {
            // 资源对象
            Resource r = new Resource();
            // 任务对象
            Input in = new Input(r);
            Output out = new Output(r);
            // 线程对象
            Thread t1 = new Thread(in);
            Thread t2 = new Thread(out);
            // 开启线程
            t1.start();
            t2.start();
        }
    }
  • 相关阅读:
    有注释的LED驱动
    给想成为程序员的大学生的建议
    三星s3c6410用户手册初步阅读
    linux下重新安装grub
    对寄存器的操作
    linux 头文件
    VC的环境设置
    VC++工程文件说明
    C/C++文件操作转载自http://www.cnblogs.com/kzloser/archive/2012/07/16/2593133.html#b1_2
    GetWindowDC-BeginPaint-GetDC 区别详解
  • 原文地址:https://www.cnblogs.com/ithome0222/p/8849827.html
Copyright © 2011-2022 走看看