• 05-线程同步


    1.概述
    当多个线程在同一时刻访问同一种共享资源时,可能会造成数据的不一致等问题;
    为了避免该问题的发生,就需要对线程之间进行协调和通信,而线程之间的协调和通信就是线程同步机制
     
     
    一个多线程的程序 如果是通过Runnable 接口来 实现的,则意味着 类中的属性将被多个线程共享,这样一来也会引发一些安全问题。
    例如,统计车间工人的数量,经常有人来回出入,很难统计正确。
    为解决这样的问题,就需要实现多线程同步,限制某个资源在某一时刻只能被一个线程访问。
     
    同步 指的是多个操作在同一时间段内,只能有一个线程进行。其他线程要等待此线程完成之后才可以继续执行。
     
     
    2.实现方式(两种)
     
    只需要将线程的 并行 改为 串行 就可以解决该问题。
    此时可以解决问题,但是效率相对比较低,因此建议能不用则不用。
     
    为了协调多个线程的执行,使用java中的 synchronized 关键字,来保证原子性。实现同步锁/对象锁。
     
    Java的内部提供了一种 锁机制 来保证 原子性。
    原子性:把整个方法的锁定的代码,全部执行完毕。 下一个线程才能执行。
     
    (1)同步方法: 使用关键字直接修饰整个方法,锁定整个方法的代码块。
     
    权限修饰符 synchronized 返回值类型 方法名([参数1,...]){ 需要同步的代码; }
     
    (2)同步代码块: 用该关键字修饰的代码块 被称为 同步代码块。
     
    synchronized(同步对象){ 需要同步的代码; }
     
    或者:
    synchronized(this){ //注意: 同步方法的锁是当前调用该方法的对象,也就是this指向的对象。 希望被锁定的代码块; }
     
    原理分析(尽量理解)
    当多个线程同时执行同一个方法时,为了避免线程之间的冲突问题,通常都会给该方法加上一把同步锁;
    当有线程先抢到同步锁时就可以进入同步语句块执行,其他线程只能进入阻塞状态;
    当该线程执行完毕同步语句块后会自动释放同步锁,阻塞的线程又可以抢占同步锁,抢占成功的线程去执行同步语句块,抢占不成功的线程继续阻塞。
     
     
     案例:解决电影院售票产生的 线程安全问题:

    问题代码:

    public class Ticket implements Runnable {               //实现Runnable接口 创建多线程 引发 线程安全问题 
        
        private int tickets=100;
        
        public void run() {
            while(true) {                                          
                try {
                    if(tickets>0) {    
                        
                    Thread.sleep(10);                                //使线程休眠
                
                    String name=Thread.currentThread().getName();   //获取当前线程的名称
                    
                    System.out.println(name+"正在发售第"+  tickets--  +"张票");  
                    
                   }else {
                      break;
                   }
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                }    
            }
        }
    }

    测试类

    public static void main(String[] args) {  
            
            Ticket ticket= new Ticket();               //创建线程任务
            
            Thread t1 = new Thread(ticket,"窗口1");    // 定义4个线程对象,代表4个售票窗口
            Thread t2 = new Thread(ticket,"窗口2");
            Thread t3 = new Thread(ticket,"窗口3");
            Thread t4 = new Thread(ticket,"窗口4");
            
            t1.start();  //开启四个线程
            t2.start();
            t3.start();
            t4.start();
            
             
        }

    结果:

        从运行结果看,不合逻辑    出现负数 ...


    解决方式一: 使用同步代码块

    public class Ticket implements Runnable {    //解决线程安全问题,使用 同步代码块(同步锁)
        private int tickets=100;
        public void run() {
    while(true) { synchronized(this) { //将this设置为 锁对象 try { if(tickets>0) { Thread.sleep(10); //使线程休眠 String name=Thread.currentThread().getName(); //获取当前线程的名称 System.out.println(name+"正在发售第"+ tickets-- +"张票"); }else { break; } } catch (InterruptedException e) { e.printStackTrace(); } } } } }

    测试代码:

    public static void main(String[] args) {  
            
            Ticket ticket= new Ticket();//创建线程任务
            
            // 定义4个线程对象,代表4个售票窗口
            Thread t1 = new Thread(ticket,"窗口1");    
            Thread t2 = new Thread(ticket,"窗口2");
            Thread t3 = new Thread(ticket,"窗口3");
            Thread t4 = new Thread(ticket,"窗口4");
            
            //开启四个线程
            t1.start();  
            t2.start();
            t3.start();
            t4.start();
        }

    问题得到解决:


    方式二:使用同步方法

    public class Ticket implements Runnable {    //解决线程安全问题  使用 同步方法             
        private int tickets=100;
        public void run() {
            while(true) {  
                saleTicket();     //调用售票方法
                if(tickets<=0) {
                    break;
                }
            }
        }
        private synchronized void saleTicket() {   //定义一个同步方法  saleTicket() 
            if(tickets>0) {
            try {
                if(tickets>0) {     
                Thread.sleep(10);                                //使线程休眠10ms
                String name=Thread.currentThread().getName();   //获取当前线程的名称
                System.out.println(name+"正在发售第"+  tickets--  +"张票");  
               }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
         }
        }
    }

    测试类:

        public static void main(String[] args) {  
            Ticket ticket= new Ticket();               //创建线程任务
            Thread t1 = new Thread(ticket,"窗口1");    // 定义4个线程对象,代表4个售票窗口
            Thread t2 = new Thread(ticket,"窗口2");
            Thread t3 = new Thread(ticket,"窗口3");
            Thread t4 = new Thread(ticket,"窗口4");
            t1.start();  //开启四个线程
            t2.start();
            t3.start();
            t4.start();
        }
     
     
     
     
     
     
     
     
     
     
     

     
    复习:
    StringBuilder类 - 后期增加的类,非线程安全的类,效率比较高 【不支持线程的同步技术。】
    StringBuffer类 - 早期就有的类,线程安全的类, 效率比较低。 【 支持线程的同步技术。】
     
     
     
    3.怎么加锁?
    只需要在 重写的run()方法返回值类型前面 加个 关键字synchronized 同步锁,表示将整个方法的所有代码 全部锁定。
    当方法体中的代码 全部执行完毕,同步锁会自动打开
    修改后的代码:(即添加了同步锁后)
    package com.monkey1030;
    
    public class Account implements Runnable {
        
        private int money = 1000; //账户余额
    
        @Override
        public synchronized  void run() {                          //   synchronized !!!!!
            
             
                // 模拟ATM机器 去后台服务器读取账户余额的过程
                
                int temp = money;   // 1000
                
                // 判断账户余额是否 >=200
                if(temp >= 200) {
                    // 模拟取款过程
                    System.out.println("正在出钞,请稍后....");
                    temp -= 200;   // 账户余额扣200:  1000 - 200 = 800
                    
                    try {
                        
                        Thread.sleep(3000);  // 休眠3秒
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("请取走您的钞票!");
                    
                    
                }else {
                    
                    System.out.println("账户余额不足,请重新核对金额!");
                    
                }
                money = temp;
             
    
        }
    
        public int getMoney() {
            return money;
        }
    
        public void setMoney(int money) {
            this.money = money;
        }
    
    }

    测试类:

    package com.monkey1030;
    
    public class Test {
    
        public static void main(String[] args) {
            
            Account acc=new Account();
            Thread t1=new Thread(acc);
            Thread t2=new Thread(acc);
            
             
            t1.start();    
            t2.start();
             
            //让主线程 等待子线程终止,然后打印账户余额
            try {
                t1.join();
                t2.join();
                 
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            
            System.out.println("账户余额是: "+acc.getMoney());
            
            
            
    
        }
    
    }

    结果:

    正在出钞,请稍后....
    请取走您的钞票!
    正在出钞,请稍后....
    请取走您的钞票!
    账户余额是: 600

     注意:

    当需要一个静态方法加锁时: “( )”括号中 不再是this
    pubic synchronized static void xxx(类名.class){ ............... }
    该方法 锁的是对象是 类对象
     
    STRINGBUFFER 是同步的 SYNCHRONIZED APPEND();
    • STRINGBUILDER 不是同步的 APPEND();
    • VECTOR 和 HASHTABLE 是同步的
    • ARRAYLIST 和 HASHMAP 不是同步的
    • COLLECTIONS.SYNCHRONIZEDLIST()
    • COLLECTIONS.SYNCHRONIZEDMAP()
    • ARRAYLIST LIST = NEW ARRAYLIST();
    • LIST SYNCLIST = COLLECTIONS.SYNCHRONIZEDLIST(LIST);
    主要取决于 类中的方法 有无Synchronized 关键字。

     二,死锁的发生

    1.概述:
    当两个线程或线程者多个互相锁定时就形成了死锁。【使用Synchronized关键字 加锁的时候,要避免死锁的发生。】
     
    2. 避免死锁的原则
     
    • 顺序上锁,反向解锁,不要回头。
    注意:
    切记尽量不要使用同步代码块的嵌套!
    • JAVA线程的API中很多过时方法都构成了死锁,因此不能调用。
     
     
    如:
    线程一执行的代码:
     
      public void run(){
            synchronized(a){      - 该线程持有对象锁a,等待对象锁b
               synachronized(b){
                  ... ...
               }
            }
        }   

    线程二执行的代码:

        public void run(){
            synchronized(b){      - 该线程持有对象锁b,等待对象锁a
               synachronized(a){
                  ... ...
               }
            }
        }   

  • 相关阅读:
    10.28
    10.29
    11.05周四
    数据库增删改查
    11.03Tuesday
    11.10
    连接数据库
    10.30
    11.04周三
    10.27
  • 原文地址:https://www.cnblogs.com/penguin1024/p/11781534.html
Copyright © 2020-2023  润新知