Java多线程[3]:线程同步(互斥)

什么是线程同步

当两个或多个线程需要访问共享资源时,它们需要以某种方式确保每次只有一个线程使用资源,实现这一目的的过程称为线程同步。Java为线程同步提供了很好的支持。

监视器的概念常用来解决线程同步问题。监视器是用做互斥锁的对象。在任何时刻,只有一个线程可以拥有监视器。当线程取得锁时,也就进入了监视器。其它所有企图进入加锁监视器的线程都会被挂起,直到第一个线程退出监视器。

在Java中,可以使用两种方法来实现线程之间的同步,这两种方法都会用到synchronized关键字。

  1. 同步方法
  2. 同步语句块

不使用线程同步导致的问题

首先我们先给出一个应该使用线程同步而没有使用的应用场景。
首先,我们定义一个工具类叫PrintUtil,用来实现打印输出的功能

public class PrintUtil {
        public static void Print(String content) {
        System.out.print("[" + content);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("]");
    }
}

然后定义一个类NewThread,实现Runnable接口,这个类在run方法中调用了PrintUtil.Print()方法。

public class NewThread implements Runnable {

    Thread t;
    String message;

    public NewThread(String msg) {
        this.message = msg;
        t = new Thread(this, "my test thread");
        t.start();
    }

    @Override
    public void run() {
        PrintUtil.Print(message);
    }
}

下面是main方法。

public class Program {

    public static void main(String[] args) {
        NewThread obj1= new NewThread("hello");
        NewThread obj2= new NewThread("java");
        NewThread obj3= new NewThread("servlet");
        try {
            obj1.t.join();
            obj2.t.join();
            obj3.t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在main方法中,实例化了三个NewThread对象,也就启动了三个线程,每个线程的run方法都调用了PrintUtil.Print() 方法。由于没有做线程同步的处理,输出的结果如下:

[hello[java[servlet]
]
]

很明显,这不是我们想要的结果

使用同步方法来实现线程同步

我们必须要限制每次只能由一个线程的run方法来调用Print()方法,并且当该方法执行完毕之后下一个线程才能调用。在Java中,想做到这个是非常容易的,只需要在PrintUtil.Print() 方法前面加上一个关键字synchronized就能轻松搞定。修改后的PrintUtil类的代码如下

public class PrintUtil {
    synchronized public static void Print(String content) {
        System.out.print("[" + content);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("]");
    }
}

其它的地方无需修改。重新运行程序,结果如下:

[hello]
[servlet]
[java]

输出的顺序可能会不一样,但左右括号都是完整的。

使用同步语句块来实现线程同步

虽然在类中创建synchronized方法是一种比较容易并且行之有效的实现线程同步的方法,但是,它并不能适用于所有场景。例如:假设PrintUtil类不是由你设计的,你也不能修改它,肿么办?同步语句块诞生了!
首先,我们将PrintUtil.Print() 方法前面的synchronized关键字去掉,然后假装我们没有权限修改这个类。然后,我们来修改NewThread的run方法,修改后的NewThread类的代码如下

public class NewThread implements Runnable {

    Thread t;
    String message;
    //这个对象其实没有卵用,只是为了对它进行加锁,因为这个对象对于多个线程来说是同一个引用
    static Object obj = new Object();
    public NewThread(String msg) {
        this.message = msg;
        t = new Thread(this, "my test thread");
        t.start();
    }

    @Override
    public void run() {     
        synchronized (obj) {
            PrintUtil.Print(message);
        }
    }
}

重新运行代码,输出的结果是

[hello]
[servlet]
[java]

下面说一下synchronized关键字后面的括号中的对象。这个对象对于多个线程来说,必须是同一个引用,通常情况下是类外部的一个对象。如果在类内部的话,必须声明为静态的。总之,它不能属于当前线程对象,要么属于外部,要么属于当前类(静态)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值