Java 多线程并发编程之 Synchronized 关键字

synchronized 关键字解析

synchronized的4种用法
1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前.即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.

 

      例如:

      public synchronized void synMethod() {

        //方法体

      }

    2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块.例如:

      public int synMethod(int a1){

        synchronized(a1) {

          //一次只能有一个线程进入

        }

      }

    3.synchronized后面括号里是一对象,此时,线程获得的是对象锁.例如:

 

public class MyThread implements Runnable {

  public static void main(String args[]) {

    MyThread mt = new MyThread();

    Thread t1 = new Thread(mt, "t1");

    Thread t2 = new Thread(mt, "t2");

    Thread t3 = new Thread(mt, "t3");

    Thread t4 = new Thread(mt, "t4");

    Thread t5 = new Thread(mt, "t5");

    Thread t6 = new Thread(mt, "t6");

    t1.start();

    t2.start();

    t3.start();

    t4.start();

    t5.start();

    t6.start();

  }

  public void run() {

    synchronized (this) {

      System.out.println(Thread.currentThread().getName());

    }

  }

}

 

 

    对于3,如果线程进入,则得到对象锁,那么别的线程在该类所有对象上的任何操作都不能进行.在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:

 

class FineGrainLock {

   MyMemberClass x, y;

   Object xlock = new Object(), ylock = new Object();

   public void foo() {

      synchronized(xlock) {

         //access x here

      }

      //do something here - but don't use shared resources

      synchronized(ylock) {

         //access y here

      }

   }

   public void bar() {

      synchronized(this) {

         //access both x and y here

      }

      //do something here - but don't use shared resources

   }

}

 

 

    4.synchronized后面括号里是类.例如:

 

class ArrayWithLockOrder{

  private static long num_locks = 0;

  private long lock_order;

  private int[] arr;

  public ArrayWithLockOrder(int[] a)

  {

    arr = a;

    synchronized(ArrayWithLockOrder.class) {//-----------------------------------------这里

      num_locks++;             // 锁数加 1。

      lock_order = num_locks;  // 为此对象实例设置唯一的 lock_order。

    }

  }

  public long lockOrder()

  {

    return lock_order;

  }

  public int[] array()

  {

    return arr;

  }

}

class SomeClass implements Runnable

{

  public int sumArrays(ArrayWithLockOrder a1,

                       ArrayWithLockOrder a2)

  {

    int value = 0;

    ArrayWithLockOrder first = a1;       // 保留数组引用的一个

    ArrayWithLockOrder last = a2;        // 本地副本。

    int size = a1.array().length;

    if (size == a2.array().length)

    {

      if (a1.lockOrder() > a2.lockOrder())  // 确定并设置对象的锁定

      {                                     // 顺序。

        first = a2;

        last = a1;

      }

      synchronized(first) {              // 按正确的顺序锁定对象。

        synchronized(last) {

          int[] arr1 = a1.array();

          int[] arr2 = a2.array();

          for (int i=0; i<size; i++)

            value += arr1[i] + arr2[i];

        }

      }

    }

    return value;

  }

  public void run() {

    //...

  }

}

 

 

    对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁.

以上4种之间的关系:

    锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。

    在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。同步化方法在执行之前获得一个锁。如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。如果这是一个实例方法,那么此锁是this对象的锁。

 


同步锁依赖于对象,每个对象都有一个同步锁。

现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的同步锁,同时,线程 B 也去调用 Test 的 synchronized 方法,此时线程 B 无法获得 Test 的同步锁,必须等待线程 A 释放 Test 的同步锁才能获得从而执行对应方法的代码。

综上,正确使用 synchronized 关键字可确保原子性。

synchronized 关键字的特性应用

特性 1:

当线程 A 调用某对象的synchronized 方法 或者 synchronized 代码块时,若同步锁未释放,其他线程调用同一对象的synchronized 方法 或者 synchronized 代码块时将被阻塞,直至线程 A 释放该对象的同步锁。

DEMO1,synchronized 方法:

public class Test {

    private static class Counter {

        public synchronized void count() {
            for (int i = 0; i < 6; i++) {
                System.out.println(Thread.currentThread().getName() + ", i = " + i);
            }
        }

    }

    private static class MyThread extends Thread {

        private Counter mCounter;

        public MyThread(Counter counter) {
            mCounter = counter;
        }

        @Override
        public void run() {
            super.run();
            mCounter.count();
        }
    }

    public static void main(String[] var0) {
        Counter counter = new Counter();
        // 注:myThread1 和 myThread2 是调用同一个对象 counter
        MyThread myThread1 = new MyThread(counter);
        MyThread myThread2 = new MyThread(counter);
        myThread1.start();
        myThread2.start();
    }

}
DEMO1 输出:

Thread-0, i = 0
Thread-0, i = 1
Thread-0, i = 2
Thread-0, i = 3
Thread-0, i = 4
Thread-0, i = 5
Thread-1, i = 0
Thread-1, i = 1
Thread-1, i = 2
Thread-1, i = 3
Thread-1, i = 4
Thread-1, i = 5
DEMO2,synchronized 代码块:

public class Test {

    private static class Counter {

        public void count() {
            synchronized (this) {
                for (int i = 0; i < 6; i++) {
                    System.out.println(Thread.currentThread().getName() + ", i = " + i);
                }
            }
        }
    }

    private static class MyThread extends Thread {

        private Counter mCounter;

        public MyThread(Counter counter) {
            mCounter = counter;
        }

        @Override
        public void run() {
            super.run();
            mCounter.count();
        }
    }

    public static void main(String[] var0) {
        Counter counter = new Counter();
        MyThread myThread1 = new MyThread(counter);
        MyThread myThread2 = new MyThread(counter);
        myThread1.start();
        myThread2.start();
    }
}
DEMO2 输出:

Thread-0, i = 0
Thread-0, i = 1
Thread-0, i = 2
Thread-0, i = 3
Thread-0, i = 4
Thread-0, i = 5
Thread-1, i = 0
Thread-1, i = 1
Thread-1, i = 2
Thread-1, i = 3
Thread-1, i = 4
Thread-1, i = 5
可见,当同步锁未释放时,其他线程将被阻塞,直至获得同步锁。

而且 DEMO1 和 DEMO2 的输出结果是一样的,synchronized 方法 和 synchronized 代码块的不同之处在于 synchronized 方法 作用域较大,作用于整个方法,而 synchronized 代码块 可控制具体的作用域,更精准控制提高效率。(毕竟阻塞的都是时间啊)

DEMO3,仅修改 main 方法:

    public static void main(String[] var0) {
        // 注意:myThread1 和 myThread2 传入的 Counter 是两个不同的对象
        MyThread myThread1 = new MyThread(new Counter());
        MyThread myThread2 = new MyThread(new Counter());
        myThread1.start();
        myThread2.start();
    }
DEMO3 输出:

Thread-0, i = 0
Thread-1, i = 0
Thread-0, i = 1
Thread-1, i = 1
Thread-1, i = 2
Thread-1, i = 3
Thread-0, i = 2
Thread-1, i = 4
Thread-0, i = 3
Thread-1, i = 5
Thread-0, i = 4
Thread-0, i = 5
同步锁基于对象,只要锁的来源一致,即可达到同步的作用。所以,但对象不一样,则不能达到同步效果。

特性 2:

当线程 A 调用某对象的synchronized 方法 或者 synchronized 代码块时,若同步锁未释放,其他线程调用同一对象的其他synchronized 方法 或者 synchronized 代码块时将被阻塞,直至线程 A 释放该对象的同步锁。(注意:重点是其他)

DEMO4,仅修改 doOtherThings 方法的修饰:

public class Test {

    private static class Counter {

        public synchronized void count() {
            System.out.println(Thread.currentThread().getName() + " sleep");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " awake");
        }

        public synchronized void doOtherThings(){
            System.out.println(Thread.currentThread().getName() + " doOtherThings");
        }
    }

    public static void main(String[] var0) {
        final Counter counter = new Counter();
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.count();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.doOtherThings();
            }
        }).start();
    }
}
DEMO4 输出:

Thread-0 sleep
Thread-0 awake
Thread-1 doOtherThings
可见,synchronized 获得的同步锁并非仅仅锁住代码,而是锁住整个对象。

此时应提及 happens-before 原则,正因 happens-before 原则的存在才有此现象的发生。
happens-before 原则的其中一条:

管理锁定原则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作。
(此处暂不作过多解释,解释起来能再写一篇文章了)

DEMO5,仅修改 doOtherThings 方法:

        public void doOtherThings(){
            synchronized (this){
                System.out.println(Thread.currentThread().getName() + " doOtherThings");
            }
        }
DEMO5 输出:

Thread-0 sleep
Thread-0 awake
Thread-1 doOtherThings
DEMO4 和 DEMO5 的输出结果竟然一致!没错,因为他们的同步锁来源一致(都是本实例自己),所以可以达到同步效果。

// 这两个 synchronized 锁的是同一个对象
public synchronized void count(){};
public void doOtherThings(){
       synchronized (this){}
}
DEMO6,去掉 doOtherThings 方法的同步关键字:

public void doOtherThings(){
            System.out.println(Thread.currentThread().getName() + " doOtherThings");
        }
DEMO6 输出:

Thread-0 sleep
Thread-1 doOtherThings
Thread-0 awake
当线程 A 调用某对象的synchronized 方法 或者 synchronized 代码块时,无论同步锁是否释放,其他线程调用同一对象的其他 非 synchronized 方法 或者 非 synchronized 代码块时可立即调用。

实例锁和全局锁

以上 DEMO 实现的都是实例锁。锁住(作用域)的是具体某一对象实例。

什么是全局锁?

锁住整个 Class,而非某个对象或实例。

注:单例型的实例锁不属于全局锁。

全局锁的实现:

静态 synchronized 方法

DEMO7:

public class Test {

    private static class Counter {

        public static synchronized void count() {
            System.out.println(Thread.currentThread().getName() + " sleep");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " awake");
        }

        public static synchronized void doOtherThings(){
            System.out.println(Thread.currentThread().getName() + " doOtherThings");
        }
    }

    public static void main(String[] var0) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Counter.count();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Counter.doOtherThings();
            }
        }).start();
    }
}
DEMO7 输出:

Thread-0 sleep
Thread-0 awake
Thread-1 doOtherThings
static 声明的方法为全局方法,与对象实例化无关,所以 static synchronized 方法为全局同步方法,与对象实例化无关。

synchronized 具体 Class 的代码块

DEMO8:

public class Test {

    private static class Counter {

        public static synchronized void count() {
            System.out.println(Thread.currentThread().getName() + " sleep");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " awake");
        }

        public void doOtherThings(){
            synchronized (Counter.class){
                System.out.println(Thread.currentThread().getName() + " doOtherThings");
            }
        }
    }

    public static void main(String[] var0) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Counter.count();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Counter counter = new Counter();
                counter.doOtherThings();
            }
        }).start();
    }
}
DEMO8 输出:

Thread-0 sleep
Thread-0 awake
Thread-1 doOtherThings
synchronized (Counter.class) 获得的同步锁是全局的,static synchronized 获得的同步锁也是全局的,同一个锁,所以达到同步效果。

区分 synchronized (this) 与 synchronized (Class.class)

DEMO9:

public class Test {

    private static class Counter {

        public void count() {
            synchronized (this){
                System.out.println(Thread.currentThread().getName() + " sleep");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " awake");
            }
        }

        public void doOtherThings(){
            synchronized (Counter.class){
                System.out.println(Thread.currentThread().getName() + " doOtherThings");
            }
        }
    }

    public static void main(String[] var0) {
        final Counter counter = new Counter();
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.count();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.doOtherThings();
            }
        }).start();
    }
}
DEMO9 输出:

Thread-0 sleep
Thread-1 doOtherThings
Thread-0 awake
synchronized (this) 获得的是具体对象实例 counter 的锁,而 synchronized (Counter.class) 获得的是全局锁,两把不同的锁,所以不能达到同步效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程和高并发是Java中非常重要的概念和技术,它们可以让Java程序更加高效地利用计算机多核CPU的性能,提升程序的并发能力,提高程序的性能和响应速度。 Java多线程机制是基于线程对象的,它允许程序同时执行多个任务,从而提高程序的并发能力。Java中的线程有两种创建方式:继承Thread类和实现Runnable接口。其中继承Thread类是比较简单的一种方式,但是它会限制程序的扩展性和灵活性,因为Java只支持单继承。而实现Runnable接口则更加灵活,因为Java支持多实现。 Java的高并发编程主要包括以下几个方面: 1. 线程池技术:线程池技术是Java中非常重要的高并发编程技术,它可以实现线程的复用,减少线程创建和销毁的开销,提高程序的性能和响应速度。 2. 锁机制:Java中的锁机制包括synchronized关键字、ReentrantLock锁、ReadWriteLock读写锁等,它们可以保证多个线程之间的互斥访问,避免数据竞争和线程安全问题。 3. CAS操作:CAS(Compare and Swap)操作是Java中一种非常高效的并发编程技术,它可以实现无锁并发访问,避免线程之间的互斥和阻塞,提高程序的性能和响应速度。 4. 并发集合类:Java中的并发集合类包括ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList等,它们可以实现线程安全的数据访问和操作,有效地提高程序的并发能力。 总之,Java多线程和高并发是Java编程中非常重要的概念和技术,掌握这些技术可以让Java程序更加高效地利用计算机多核CPU的性能,提升程序的并发能力,提高程序的性能和响应速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值