[19/04/09-星期二] 多线程_线程同步

一、概念

      现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。

天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。

      处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。

线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,

下一个线程再使用。

         由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决

这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。

      由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,

这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。

▪ synchronized 方法

      通过在方法声明中加入 synchronized关键字来声明,语法如下:

public  synchronized  void accessVal(int newVal);


锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。     

 synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的

/***
 * synchronized [英语]同步的。 线程锁,保证数据准确
 * 线程安全,在并发时要保证数据准确,效率尽可能高
 * 1、在某个方法前面加锁,即同步方法
 * 2、同步块,你用那块给那块加锁。
 */
package cn.sxt.thread;
//使用同步方法
public class Test_0409_Synchronized {
    public static void main(String[] args) {
        SafeWeb12306 web=new SafeWeb12306();

        //start()表示web对象调用run方法去了,不知什么时候回来,主方法继续往下不等它.又来个线程也去调用run方法去了
        new Thread(web,"1号黄牛").start();//3个线程相互独立
        new Thread(web,"2号黄牛").start();            
        new Thread(web,"3号黄牛").start();    
    }

}
class  SafeWeb12306 implements Runnable{
    private int ticketNum=10;
    private boolean flag=true;

    public void run() {
        while (flag) {
            buyTicket1();
        }
    }
    //synchronized同步方法锁定的是与对象web相关的资源,ticketNum和flag都是它的资源,但有些时候可能存在锁的对象不对,仍然达不到效果
    public synchronized void buyTicket() {//加了同步锁的方法
        if (ticketNum<=0) {/*1、没加锁之前为什么出现负数票,当只有临界值即最后余票1张(编号001),当线程B进来时发现1不满足余票为0的情形
         * 线程B拿了最后一张票,遇到网络延时200毫秒,然后睡觉去啦,但是还没到最后一步即修改内存中票的余数,就是这个语句ticketNum--
         * (Thread.currentThread().getName()+"->"+ticketNum--) 此时线程A也进来发现还有一张票(没修过票的余数),于是他也拿一个
         * 去睡觉去啦,同理线程C也进来了,也满足情况,也拿一张睡觉去啦。等到线程B先醒来,拿着票去修改内存票数(ticketNum--),
         * 输出1,A和C分别醒来,看到内存中的ticketNum分别为1和0,然后修改票的余数,然后输出0和-1
         * 2、出现票号相同的情况
         * 每个线程都独立的工作空间,当线程A把一张票的信息(假设是8)复制到自己空间是遇到网络延时,还没等把减去一张的信息(7)覆盖回去
         * 线程B此时又把原来票的余数为8的信息复制到自己的空间去了,所以出现2个8
         * */
            System.out.println("没有余票了!");
            flag=false;
            return;        
        }
        try { //模拟网络延时 200毫秒延时 
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //Thread.currentThread().getName(),当前线程的名字,谁运行就输出谁的名字
        System.out.println(Thread.currentThread().getName()+"->"+ticketNum--+"号票");
    }


    //在保证安全的情况下提高性能的方式
    public  void buyTicket1() {//加了同步锁的方法

        if (ticketNum<=0) {//双重检测,考虑没有票的情形
            flag=false;//在明确已经没票后,线程C执行到这里判断一下,没票就退出来,不用走到synchronized的排队队列中
            return;        //傻傻的等
        }
        synchronized (this) {//锁this对象,有ticketNum和flag2个属性,单锁一个锁不住.synchronized (属性1,属性2)是错误写法 
            if (ticketNum<=0) {//在有票的情况下,考虑最后一张票的情形,当线程A拿到同步锁后(在此之后谁也进不来),拿完票后
                //把ticktNum减1后变为0,线程A解锁后,线程B进来只用判断一下,已经没票了,直接退出,不用执行下边的延时操作
                //提升性能
                flag=false;
                return;        
            }
            try { //模拟网络延时 200毫秒延时 
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //Thread.currentThread().getName(),当前线程的名字,谁运行就输出谁的名字
            System.out.println(Thread.currentThread().getName()+"->"+(ticketNum--)+"号票");

        }

    }

}

 

 

▪ synchronized块

      synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。

      Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。

      synchronized 块:通过 synchronized关键字来声明synchronized 块,语法如下:

synchronized(syncObject)
   { 
   //允许访问控制的代码 
   }

 

 

/***
 * 使用同步块
 * synchronized (obj) {
            
        },obj称为同步监视器,它可以是任何对象,推荐使用共享资源作为同步监视器
    无需指定同步监视器,因为同步方法的同步监视器是this即该对象的本身或class即类的模子
 * 
 * 过程:
 * 1、线程A访问,锁定同步监视器,执行其中的代码
 * 2、线程B访问,发现同步监视器被锁定,无法访问
 * 3、线程A访问完,解锁同步监视器
 * 4、线程B访问,发现同步监视器未锁,锁定并访问
 */
package cn.sxt.thread;

public class Test_0409_Synchronized_Drawing {
    public static void main(String[] args) {
        Account account=new Account("结婚基金", 100);
        Drawing me=new Drawing(account, 70, "小李");
        Drawing she=new Drawing(account, 30, "媳妇");
        me.start();
        she.start();    
    }
}
//银行账户
class Account{
    String name;
    int money;

    public Account(String name, int money) {
        super();
        this.name = name;
        this.money = money;
    }    
}

//取钱的过程
class Drawing extends Thread{
    Account account;//取钱的账户
    int drawingMoney;//每一笔取得钱数
    int pocketMoney;//可能取多笔,总的取钱数

    
    public Drawing(Account account, int drawingMoney,String name) {//String name取钱的姓名
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    public void run() {
        core();
        
    }
    //目标锁定即锁定账户余额。synchronized块锁
    public  void core() {
        if (account.money<=0) {//提高性能,先看账户有没有钱,没有就直接跳出,当然不可能为0,避免cpu调度
            System.out.println("账户没钱了,穷逼!");
            return;        
        }    
        /*一直到执行完synchronized块的最后一句System.out.println(this.getName()+"-->口袋的钱:"+ pocketMoney);
         * 才会解锁
         * */
        //提出问题:有性能问题,即使账户没钱了,线程也需要等然后就进来判断一下,看看是不是没钱了
        synchronized (account) {//加了锁之后,如果第2个线程进来发现account.money-drawingMoney<0就会跳出
            //不会往下执行
            if (account.money >0) {
                System.out.println("结婚基金的余额:"+account.money);
            }else {
                System.out.println("账户没钱了,结个毛婚!");
            }
            
            
            if (account.money-drawingMoney<0) {
                return;        
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money=account.money-drawingMoney;//账户总额减去要取得钱数
            pocketMoney=pocketMoney+drawingMoney;//每取一笔,口袋的钱加一笔
            System.out.println(this.getName()+"-->取完后账户余额:"+ account.money);
            System.out.println(this.getName()+"-->现在口袋里的钱:"+ pocketMoney);        
        }        
    }
    
}

 

 

 

posted @ 2019-04-09 01:17  ID长安忆  阅读(137)  评论(0)    收藏  举报