多线程编程中保证线程安全

这篇文章是原来上面向对象(OO)课时的一个博客,这里直接搬过来汇总到这里,下面是原内容。


今天上课的时候,在测试一段代码时想到了一点简单的保证线程安全的东西。首先是待测试的代码:

public class ThreadCount {
    public static void main(String[] args) {
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread();
            threads[i].start();
        }
    }
}
 
class AThread extends Thread{
    public void run(){
        Counter counter = new Counter();
        System.out.println(Counter.calNum());
    }           
}
 
class Counter{
    private static long num;
    public Counter(){
        num++;
    }
    public static long calNum(){
        return num;
    }
}

很明显,这段代码中共享的临界资源就是Counter中num变量,在不同的线程中对num进行++操作,但是在输出的时候不能及时更新。 所以我们要处理的就是如何保持保证这个num的同步使用(只有在输出之后才允许其他线程使用Counter的构造方法)。

方法一:直接锁住Counter这个类(其实就是保证其中的static变量只能有一个线程在访问)

public class ThreadCount {
    public static void main(String[] args) {
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread();
            threads[i].start();
        }
    }
}
 
class AThread extends Thread{
    public void run(){
        synchronized (Counter.class) {
            Counter counter = new Counter();
            System.out.println(Counter.calNum());
        }
        
    }           
}
 
class Counter{
    private static long num;
    public Counter(){
        num++;
    }
    public static long calNum(){
        return num;
    }
}

方法二:(构造一个监视对象专用用来保证代码的连续运行,这一点在很多时候很有用;并且有了这个监视对象之后,可以灵活地对代码块进行同步),从下面的例子中我们可以看到,如果这些线程都公用一个ThreadMonitor则可以随时利用这个Object来对代码块进行锁定。

public class ThreadCount {
    public static void main(String[] args) {
        Object ThreadMonitor = new Object();
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread(ThreadMonitor);
            threads[i].start();
        }
    }
}
 
class AThread extends Thread{
    Object ThreadMonitor = null;
    public AThread(Object ThreadMonitor){
        this.ThreadMonitor = ThreadMonitor;
    }
    public void run(){
        synchronized (ThreadMonitor) {
            Counter counter = new Counter();
            System.out.println(Counter.calNum());
        }       
    }           
}
 
class Counter{
    private static long num;
    public Counter(){
        num++;
    }
    public static long calNum(){
        return num;
    }
}

方法三:其实这种方法和方法二没有什么区别,可以直接使用Lock对象来对代码块,进行锁定,但是由于是多个进程,所以要把Lock对象设为静态static变量,这样所有的线程都是共用一个Lock对象,从而形成互斥访问。

public class ThreadCount {
    public static void main(String[] args) {
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread();
            threads[i].start();
        }
    }
}
 
class AThread extends Thread{
    private static Lock runlock = new ReentrantLock();
    
    public void run(){
        runlock.lock();
        try{
            Counter counter = new Counter();
            System.out.println(Counter.calNum());
        } finally{
            runlock.unlock();
        }
    }           
}
 
class Counter{
    private static long num;
    public Counter(){
        num++;
    }
    public static long calNum(){
        return num;
    }
}

现在对多线程的理解还很幼稚,所以应该多想想如何使用。其实这里的方法二是一种很好的方式,对于临界资源的访问可以使用一个Monitor来监视,这样就能互斥访问。

本文遵守 CC-BY-NC-4.0 许可协议。

Creative Commons License

欢迎转载,转载需注明出处,且禁止用于商业目的。

上篇云计算对传统软件工程的影响