4、基本的线程同步

来源:互联网 发布:淘宝开学季什么时候 编辑:程序博客网 时间:2024/04/28 08:32

monitor

他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

关键字synchronized

Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
有四种不同的同步块:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

修饰一个代码块

public class MyThread implements Runnable {    private int count=0;    @Override    public void run() {        for (int i = 0; i < 5; i++) {            try {                System.out.println(Thread.currentThread().getName() + ":" + count++);                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}public class ThreadTest {    public static void main(String[] args) {        MyThread thread = new MyThread();        Thread thread1 = new Thread(thread, "SyncThread1");        Thread thread2 = new Thread(thread, "SyncThread2");        thread1.start();        thread2.start();    }}

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

我们再把Thread的调用稍微改一下:

Thread thread1 = new Thread( new MyThread(), "SyncThread1");Thread thread2 = new Thread( new MyThread(), "SyncThread2");thread1.start();thread2.start();

上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联

而上面的代码等同于下面这段代码:

SyncThread syncThread1 = new SyncThread();SyncThread syncThread2 = new SyncThread();Thread thread1 = new Thread(syncThread1, "SyncThread1");Thread thread2 = new Thread(syncThread2, "SyncThread2");thread1.start();thread2.start();

这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。

修饰一个方法

Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

public synchronized void run() {   for (int i = 0; i < 5; i ++) {      try {         System.out.println(Thread.currentThread().getName() + ":" + (count++));         Thread.sleep(100);      } catch (InterruptedException e) {         e.printStackTrace();      }   }}

修饰一个静态的方法

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。

class SyncThread implements Runnable {   private static int count;   public SyncThread() {      count = 0;   }   public synchronized static void method() {      for (int i = 0; i < 5; i ++) {         try {            System.out.println(Thread.currentThread().getName() + ":" + (count++));            Thread.sleep(100);         } catch (InterruptedException e) {            e.printStackTrace();         }      }   }   public synchronized void run() {      method();   }}

测试代码

SyncThread syncThread1 = new SyncThread();SyncThread syncThread2 = new SyncThread();Thread thread1 = new Thread(syncThread1, "SyncThread1");Thread thread2 = new Thread(syncThread2, "SyncThread2");thread1.start();thread2.start();

thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。

总结

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

原创粉丝点击