java多线程与线程并发二:线程互斥

本文章内容整理自:张孝祥_Java多线程与并发库高级应用视频教程

当两条线程访问同一个资源时,可能会出现安全隐患。以打印字符串为例,先看下面的代码:

//
public class Test2 {
    public static void main(String[] args) {
        new Test2().init();
    }
    
    public void init(){
        final Outputer c = new Outputer();
        Thread thread1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                
                while (true) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    c.output("aaaaa");
                }
                
            }
        });
        thread1.start();
        Thread thread2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    c.output("bbbbb");
                }
                
            }
        });
        thread2.start();
    }
    
    class Outputer{
        public void output(String name){

                for (int i = 0; i < name.length(); i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();

        }
    }
}

在以上代码中,thread1和thread2都调用了output方法,理想的情况下,当thread1调用output方法时,应该一次性打印完“aaaaa”,而当thread2调用output方法,也是一次性打印完“bbbbb”。但在实际的运行中,会出现打印出诸如“aabbbaaa”这种情况,也就是说,thread1正在调用output方法时,thread2插了进来,两个线程同时操作了一个对象中的同一个方法。

为了避免这种情况,我们要求,当某个线程在调用output方法时,不允许其他线程调用。概括一下,即当某个线程在执行一段代码时,不允许其他线程执行这段代码。我们把这种行为,叫做线程互斥。

要实现线程互斥,最简单的方法就是加同步锁。

加同步锁的方式有两种。

第一种是同步代码块,以上面的代码为例,加同步代码块应该这么加:

    class Outputer{
        public void output(String name){
            synchronized (this) {
                for (int i = 0; i < name.length(); i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }
        }
    }

在上面的代码中,“synchronized (this)”中的“this”被称为锁栓(也有别的叫法,在这里姑且先这么叫吧)。多个需要互斥的线程必须使用同一个对象当锁栓。在这段代码中,“this”指Outputer对象,而两条线程使用的是同一个Outputer对象,所以可以达到互斥效果。

第二种方式是声明同步方法,写法如下:

        public synchronized void output2(String name){
            System.out.println(this.getClass());
            for (int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }

现在有一个问题,加入thread1调用output1,thread2调用output2,可以实现线程互斥吗?

答案是可以的。

同步方法使用的锁栓就是“this”。当同步代码块也使用“this”当锁栓时,这两个地方的代码就共用了同一个对象当锁栓,当某个线程进入其中一个时,另一个也被锁住了,不让其他线程进来。

那么,静态同步方法使用的锁栓是什么呢?

以下面这段代码为例:

    static class Outputer{
        public static synchronized void output3(String name){
            for (int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }
    }

它的同步锁是Outputer.class,即类的字节码对象。

最后说一说线程同步和线程互斥的关系。

同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

  也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!

  总结:

  互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

  同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥。

原文地址:https://www.cnblogs.com/bailiyi/p/3619983.html