码迷,mamicode.com
首页 > 编程语言 > 详细

Java学习笔记之多线程二

时间:2016-04-16 16:54:40      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:

  看到一篇讲线程的故事性文章,觉得很有意思,很佩服作者能这么生动地讲述出来,点击可跳转阅读此文章:《我是一个线程》

  

  继续我的笔记中总结 - -

    理解线程安全问题:

  下面是书上看到的卖票例子:模拟3个窗口同时在售10张票。

  上篇博文笔记总结了多线程创建的两种方式,那我们就分别以这两种实现多线程的方式来解决这个场景。

  • 使用继承于Thread类的方式

  上Demo:

class SaleTicket extends Thread {
    int num = 10;  // 票数
    
    public SaleTicket(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
            } else {
                System.out.println("售罄了...");
                break;
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //创建三个线程对象
        SaleTicket th1 = new SaleTicket("A");
        SaleTicket th2 = new SaleTicket("B");
        SaleTicket th3 = new SaleTicket("C");
        //开启线程
        th1.start();
        th2.start();
        th3.start();
    }
}

   Dmeo1执行结果图为:

  技术分享

  从实验结果得到,本来10张的门票却卖出了30张,Why?

      原来,这是因为:票数 num 声明为了一个非静态成员变量,而非静态成员变量是在每个对象中都会维护一份数据的。那么创建了三个线程对象就有三个num变量了~即我们这样要使得票数num为三个线程共享一份,因而需要将票数num变量设置为静态成员变量。即:
static int num = 10;//票数  非静态的成员变量,非静态的成员变量数据是在每个对象中都会维护一份数据的。

  改完后再运行之,结果如下:

    技术分享

  这时候发现确实输出的是10条记录(除去头三条可能出错的三条),说明static起作用,达到了共享的目的。

      但细细一看,新的问题随之而来,A、B、C窗口居然会出现售出同一张票的情况!Why?

  这便搭上了今天的主题了,线程安全问题。

  分析如下:

  假设首先A线程抢夺了cpu的执行权,开始执行,当执行到

System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );

   后(还未执行num--),num为10,此时B线程抢占了cpu执行权开始执行,同样执行到

System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );

   后,num为50,C线程抢夺了cpu执行权,C同样执行到AB执行的相同的代码处,此时ABC三者num值都为10;因而才会出现以上现象。

   那么就得考虑,如何保证当一个线程在执行整块的代码时,不受其他线程的干扰?

     于是乎,Java提供了一种同步机制来解决线程安全问题。
 

   同步机制分为同步代码块和同步方法

   先来看同步代码块的格式:

synchronized(锁对象){
    //需要被同步的代码
    ...
}

  当一个线程要使用火车票这个资源时,我们就交给它一把锁,等它把事情做完后再把锁给另一个需要用这个资源的线程.

  此时将上面分析的有可能引发线程安全问题的代码放在synchronized代码块中:
synchronized (SaleTicket.class) {
    if (num > 0) {
        System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票" );
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num--;
    } else {
            System.out.println("售罄了...");
        break;
    }
}

  再次运行之:

    技术分享

  由此得到了我们想要的正确结果。

  同步代码块要注意的事项:

    1. 锁对象可以是任意的一个对象。
    2. 一个线程在同步代码块中sleep了,并不会释放锁对象。
    3. 锁对象必须是多线程共享的一个资源,否则锁不住。
    4. 如果不存在着线程安全问题,千万不要使用同步代码块,因为会降低效率。
 
  同步代码块也可以使用同步方法的方式来实现:
  同步方法即将可能会发生线程安全问题的代码置于一个函数中,上面的实现方式为:
public void run() {
    while (true) {
        if (num > 0) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.sale();
        } else {
            break;
        }            
    }
}
// 同步方法
public synchronized void sale() { if(num > 0) { System.out.println(Thread.currentThread().getName() + "窗口售出了第" + (num--) + "号票" ); } }

  同步函数要注意的事项 :

    1. 如果是一个非静态的同步函数的锁 对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
    2. 同步函数的锁对象是固定的,不能随便指定的。
 

  那么,对于这两种实现同步机制的方式,推荐使用的是:同步代码块

  原因也很简单:  

    1. 同步代码块的锁对象可以由我们随意指定,方便控制。而同步方法的锁对象是固定的。
    2. 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数 的所有代码都被同步了。
 
  • 使用实现Runnable接口的方式:

  Demo2:

class SaleTicket2 implements Runnable {
    int num = 10;
    
    @Override
    public void run() {
        while (true) {
            synchronized (SaleTicket2.class) {
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "窗口售出了第" + num + "号票");
                    num--;
                } else {
                    break;
                }                
            }
        }
    }
}
public class Demo2 { public static void main(String[] args) { //创建了一个Runnable实现类的对象 SaleTicket2 saleTicket = new SaleTicket2(); //创建三个线程对象模拟三个窗口 Thread th1 = new Thread(saleTicket, "A"); Thread th2 = new Thread(saleTicket, "B"); Thread th3 = new Thread(saleTicket, "C"); //开启线程售票 th1.start(); th2.start(); th3.start(); } }

  运行之,结果如下:

     技术分享

  同样也得到了正确的结果。

  总结:

  引发线程安全问题的根本原因是什么?

    1. 存在两个或两个以上的线程对象,并且线程之间共享同一个资源。

    2. 有多条语句操作了共享资源。

  解决方案:

    使用同步机制来解决,即通过同步代码块(推荐)或同步方法将可能引发线程安全问题的代码段“锁”住。

Java学习笔记之多线程二

标签:

原文地址:http://www.cnblogs.com/crayon-v/p/5398655.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!