java并发(synchronized)

来源:互联网 发布:关于windows api的书籍 编辑:程序博客网 时间:2024/06/16 17:48

一.synchronized的可重用性

当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞.

我们来看看synchronized,它拥有强制原子性的内置锁机制,是一个重入锁,所以在使用synchronized时,当一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁,就是说在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以拿到锁,如下:

public class Child extends Father {    public static void main(String[] args) {        Child child = new Child();        child.doSomething();    }    public synchronized void doSomething() {        System.out.println("child.doSomething()");        doAnotherThing(); // 调用自己类中其他的synchronized方法    }    private synchronized void doAnotherThing() {        super.doSomething(); // 调用父类的synchronized方法        System.out.println("child.doAnotherThing()");    }}class Father {    public synchronized void doSomething() {        System.out.println("father.doSomething()");    }}

结果应该很清楚了。
这里的对象锁只有一个,就是child对象的锁,当执行child.doSomething时,该线程获得child对象的锁,在doSomething方法内执行doAnotherThing时再次请求child对象的锁,因为synchronized是重入锁,所以可以得到该锁,继续在doAnotherThing里执行父类的doSomething方法时第三次请求child对象的锁,同理可得到,如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。

所以在java内部,同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每线程(per-thread)”,而不是基于“每调用(per-invocation)”的(java中线程获得对象锁的操作是以每线程为粒度的,per-invocation互斥体获得对象锁的操作是以每调用作为粒度的)

我们再来看看重入锁是怎么实现可重入性的,其实现方法是为每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁。

import java.util.ArrayList;public class ReentrancyTest {    static public int i = 1;    public int count ;    public ReentrancyTest() {        super();    }    public static void main(String[] args) {        int threadNum = 10; // 设置10个线程同时执行        // 每个线程都关联同一个ReentrancyTest对象        ReentrancyTest reentrancyTest = new ReentrancyTest();         ArrayList<MyThread> threadList = new ArrayList<MyThread>();         // 为10个线程赋值同一个ReentrancyTest对象的引用        for (int i = 0; i < threadNum; i++)        {            MyThread myThread = new MyThread();            myThread.reentrancyTest = reentrancyTest;            threadList.add(myThread);        }         // 启动10个线程        for (int i = 0; i < threadNum; i++) {            new Thread((MyThread) threadList.get(i)).start();        }    }    public void doSomething() {         //随机产生一个睡眠时间        int sleep=(int)(Math.random()*500);             try {            Thread.sleep(sleep);         } catch (InterruptedException e) {            e.printStackTrace();        }        count = i++;        System.out.println("第"+count+"个线程:"+Thread.currentThread().getName()                + "进入到doSomething()执行代码--睡眠"+sleep+"毫秒");        try {            Thread.sleep(sleep);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+ "线程执行doSomething()完毕,睡眠时间:"                + sleep+",已进入doSomething执行代码的线程总数为:"+count);    }}class MyThread extends Thread {    public ReentrancyTest reentrancyTest;    public MyThread() {        super();    }    @Override    public void run() {        reentrancyTest.doSomething();        super.run();    }}

该代码doSomething加不加synchornized会有不同的结果。

多线程在操作同一对象时,如果对象中的函数不是同步的,多线程可以并发执行此函数

不同线程对同一对象锁是要竞争的,是同步阻塞模式,不能像同一线程对同一对象锁是可重入的!


二.synchronized与lock的比较

  • 两者用法不同:对需同步的对象加synchronized控制,既可以加在方法上,也可以加在特定的代码块中,lock需要显式的制定起始位置与终止位置。
  • 性质不一样:在资源竞争不激烈的情况下synchronized的性能更好,竞争激烈的情况下lock更优。
  • 锁的机制不一样:synchronized获得和释放锁的方法都是在块结构中,顺序相反,自动解锁,一般不会引发死锁,lock需要人工释放,最好在finally块中释放,否则容易死锁。
0 0