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

黑马程序员------Java中多线程学习总结(二)

时间:2015-02-23 10:50:21      阅读:182      评论:0      收藏:0      [点我收藏+]

标签:

Java培训、Android培训、iOS培训、.Net培训,期待您的交流

在Java多线程中,如果有多个线程同时操作共享数据时,就可能会发生数据异常

如下面这段代码:

技术分享
/*
 * 模拟卖票
 */
class Ticket implements Runnable
{
    private  int tick = 10;
    Object obj = new Object();
    public void run()
    {
        while(true)
        {            
            if(tick>0)
            {
                try{Thread.sleep(10);}catch(Exception e){}
                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
            }
        }
    }
}

class  TicketDemo2
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
技术分享

这段代码的运行结果:

技术分享
Thread-2....sale : 10
Thread-0....sale : 9
Thread-1....sale : 8
Thread-3....sale : 7
Thread-2....sale : 6
Thread-0....sale : 5
Thread-1....sale : 4
Thread-3....sale : 3
Thread-2....sale : 2
Thread-0....sale : 1
Thread-3....sale : 0
Thread-2....sale : -1
Thread-1....sale : -2
技术分享

通过结果可以看出,票数出现了负数,这就出现了数据异常,也就是程序中出现了“脏数据”。

这种“脏数据”产生的原因是在多线程同时操作共享数据时而产生的,若想避免“脏数据”,就得保证在一个线程操作共享数据时,其他线程不能够操作共享数据。

解决这种问题可以使用synchronied关键字来实现同步。

即在需要同步的代码上加上synchronizd关键字:

技术分享
public void run()
    {
        while(true)
        {    
            //此处加上synchronized关键字
            synchronized (obj) {
                if(tick>0)
                {
                    try{Thread.sleep(10);}catch(Exception e){}
                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
                }
            }    
        }
    }
技术分享

被synchronized关键字修饰的代码块叫做同步代码块。

它也可以修饰方法,代码如下:

public synchronized void run()
{
     
} 

 从上面的代码可以看出,只要在void和public之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。

也可以用来修饰静态方法:

class Test 
{
    public static synchronized void method() {   }
} 

我们熟知的单例设计模式延迟加载方式是线程不安全的,代码如下:

技术分享
// 线程不安全的Singleton模式
class Singleton
{
    private static Singleton sample;

    private Singleton()
    {
    }
    public static Singleton getInstance()
    {
        if (sample == null)
        {
            Thread.yield(); // 为了放大Singleton模式的线程不安全性
            sample = new Singleton();
        }
        return sample;
    }
}
public class SingletonTest extends Thread
{
    public void run()
    {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton.hashCode());
    }
    public static void main(String[] args)
    {
        Thread threads[] = new Thread[5];
        for (int i = 0; i < threads.length; i++)
            threads[i] = new SingletonTest();
        for (int i = 0; i < threads.length; i++)
            threads[i].start();
    }
} 
技术分享

 程序的运行结果如下:

1794515827
1167165921
1442002549
1701381926
1442002549

上面的运行结果可能在不同的运行环境上有所有同,但一般这五行输出不会完全相同。从这个输出结果可以看出,通过getInstance方法得到的对象实例是五个,而不是我们期望的一个。 

这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了另外一个线程。 由于在线程之间切换时并未执行到创建Singleton对象实例的语句,因此,这几个线程都通过了if判断, 所以,就会产生了建立五个对象实例的情况(可能创建的是四个或三个对象实例,这取决于有多少个线程在创建Singleton对象之前通过了if判断,每次运行时可能结果会不一样)。要想使上面的单件模式变成线程安全的,只要为getInstance加上synchronized关键字即可。代码如下:

public static synchronized Singleton getInstance() {   }

当然,还有更简单的方法,就是在定义Singleton变量时就建立Singleton对象,即我们俗称的“饱汉式”,代码如下:

private static final Singleton sample = new Singleton(); 

需要理解的是,无论此关键字修饰的是非静态方法还是静态方法,被锁定的不是这些方法,而是这些方法所在的对象或类。

使用synchronized关键字需要注意synchronized关键字的作用域:

1、是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

2、是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

黑马程序员------Java中多线程学习总结(二)

标签:

原文地址:http://www.cnblogs.com/alvis2015/p/4297888.html

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