博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[19/04/09-星期二] 多线程_线程同步
阅读量:5050 次
发布时间:2019-06-12

本文共 5999 字,大约阅读时间需要 19 分钟。

一、概念

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

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

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

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

下一个线程再使用。

         由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。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); } } }

 

 

 

转载于:https://www.cnblogs.com/ID-qingxin/p/10674589.html

你可能感兴趣的文章
全然同态加密
查看>>
php 接口类与抽象类的实际作用
查看>>
Beta答辩总结
查看>>
fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
查看>>
[模板]洛谷T3383 线性筛素数 欧拉筛法
查看>>
ubuntu 配置拼音输入法步骤
查看>>
python:OS模块
查看>>
2014北京站小记
查看>>
天购新玩法 引领电商发展新潮
查看>>
网上删除所有数据文件的恢复情况
查看>>
Linux--安装过程中的根文件系统的分析
查看>>
一步一步写算法(之hash表)
查看>>
Java中Map的使用
查看>>
java内存分析总结
查看>>
EBS R12 LOG files 位置
查看>>
《集体智慧编程》学习笔记
查看>>
其中imagelist.txt和train.txt的格式如下注释所示
查看>>
手机端rem
查看>>
U-editor文件上传
查看>>
selenium+python之iframe学习笔记
查看>>