精华内容
下载资源
问答
  • synchronized(this) ...t2才能进去执行,若去掉synchronized(this),则t1和t2随时都可以进来执行此段代码中的任何一步,时间到了另一个接着进来执行 synchronized 修饰方法 用范围是整个方法,所以方法中所有的.

    synchronized(this)

    //此处this指的是进入此代码块的线程对象(也就是那个对象调用了该方法,就表示当前锁是那个对象),如果t1进来了,那么锁住t1,若t1时间片结束了,t2走到此处也只能在上一句代码处等待t1获得了时间片后执行完synchronized锁住的所有代码,t2才能进去执行,若去掉synchronized(this),则t1和t2随时都可以进来执行此段代码中的任何一步,时间到了另一个接着进来执行

     

    synchronized 修饰方法

    用范围是整个方法,所以方法中所有的代码都是同步的

    对于非静态方法,同一个实例的线程访问会被拦截,非同一实例可以同时访问。 即此时是默认对象锁(this)

    对于静态方法,静态方法默认类锁。

     

    采用类锁,无所谓哪个类,都会被拦截

     

    synchronized (this)举例说明 :

     

    public class SynchronizedTest {

     

        private void method1() {

            synchronized (this) {

                System.out.println("method1当前线程:" + Thread.currentThread().getName());

                System.out.println("method1执行过程1");

                System.out.println("method1执行过程2");

                System.out.println("method1执行过程3");

                System.out.println("method1执行过程4");

                System.out.println("method1执行过程5");

                System.out.println("method1执行过程6");

            }

     

        }

     

        public void test1() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method1();

                }

            }.start();

     

        }

     

     

     

    }

    首先我们定义一个SynchronizedTest 类,然后里面定义两个方法,method1方法只是 打印几行,相当于该方法的执行过程,test1方法里new 了一个线程去执行method1方法

    如果我们进行如下调用会如何打印:

     

    通过打印我们发现线程不是同步的,为什么?难道synchronized没有起作用?其实并非如此,是因为synchronized中的this每次都不是同一个对象,也就是不是同一个锁,自然10个线程执行的时候也就不会相互干扰,因为我们每次执行test1()的时候都是new SynchronizedTest()一个新的对象,自然线程执行的时候也就是使用的不同的锁。自然不会同步。

     

    好,现在我们换一种执行策略,上代码:

     

    现在我们观看打印:

     

    可以看到,不管我执行多少边该代码,该方法中的代码块都是顺序执行的,换句话说,每次只能有一个线程执行该方法,而且必须等到该方法只想完成,才能让下一个线程执行,也就是打印所示,method1方法必须一次从过程1执行到过程6,才能换下个线程执行该方法。为什么?这次又和上面不一样呢?因为这次我们只是创建了一个SynchronizedTest对象,循环10次调用test1的时候每次也都是用同一SynchronizedTest对象,自然synchronized中的this指的是这同一个对象,换句话说这10个线程一直使用的是同一把对象锁,所以必须等到持有锁的线程将自己的代码执行完才能将锁释放出来,交个下一个线程执行,这样的话,该方法自然顺序执行的。

    如果将加锁的方式改为直接加在方法上,道理是和SynchronizedTest(this)一样的

    如上的写法和synchronized(this)是一个意思。运行效果是一样的,上面的锁都表示执行该线程的的对象。

     

     

    现在我们继续修改代码:

    在synchronized 中添加两个方法

    public class SynchronizedTest {

     

        private void method1() {

            synchronized (this) {

                System.out.println("method1当前线程:" + Thread.currentThread().getName());

                System.out.println("method1执行过程1");

                System.out.println("method1执行过程2");

                System.out.println("method1执行过程3");

                System.out.println("method1执行过程4");

                System.out.println("method1执行过程5");

                System.out.println("method1执行过程6");

            }

     

        }

     

        public void test1() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method1();

                }

            }.start();

     

        }

     

     

        private void method2() {

            synchronized (this) {

                System.out.println("method2当前线程:" + Thread.currentThread().getName());

                System.out.println("method2执行过程1");

                System.out.println("method2执行过程2");

                System.out.println("method2执行过程3");

                System.out.println("method2执行过程4");

                System.out.println("method2执行过程5");

                System.out.println("method2执行过程6");

            }

     

        }

     

        public void test2() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method2();

                }

            }.start();

     

        }

     

     

    }

     

    然后我们做如下测试:

    接着我们看打印:

    method1当前线程:Thread-0

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-1

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-11

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-10

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-7

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

     

    从打印结果中,我们可以查出,同一时间也是只能执行同一方法,因为这两个方法用的都是同一个锁对象,所以同一时间,要么执行method1方法,要么执行method2方法。

    现在我们修改一下测试案例:

    method1当前线程:Thread-0

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-1

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-4

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method2当前线程:Thread-5

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1执行过程4

    method2当前线程:Thread-9

    method2执行过程1

    method2执行过程2

    method1执行过程5

    method1执行过程6

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-2

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-13

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-8

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-11

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-18

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-7

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-17

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-14

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-3

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-10

    method1执行过程1

    method2当前线程:Thread-19

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-15

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-12

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-16

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-6

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

     

    从执行结果可以看出,两个方法不再是顺序执行的,换句话说,同一时间,我两个方法可能是一起执行的,因为这两个方法使用的已经不是同一个对象锁了,一个是synchronizedTest1 一个是synchronizedTest2对象。

    但是,请一定要记住但是,每个方法自己内部的代码块是顺序执行的,也就是,每个方法各自里面代码的执行过程肯定是从过程1到过程6,然后再继续是过程1到过程6.因为,每个方法本身的对象锁一直是一个。

     

     

    synchronize类锁举例说明:

    现在我们改变一下代码:

    public class SynchronizedTest {

     

        private void method1() {

            synchronized (SynchronizedTest.class) {

                System.out.println("method1当前线程:" + Thread.currentThread().getName());

                System.out.println("method1执行过程1");

                System.out.println("method1执行过程2");

                System.out.println("method1执行过程3");

                System.out.println("method1执行过程4");

                System.out.println("method1执行过程5");

                System.out.println("method1执行过程6");

            }

     

        }

     

        public void test1() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method1();

                }

            }.start();

     

        }

     

     

        private void method2() {

            synchronized (SynchronizedTest.class) {

                System.out.println("method2当前线程:" + Thread.currentThread().getName());

                System.out.println("method2执行过程1");

                System.out.println("method2执行过程2");

                System.out.println("method2执行过程3");

                System.out.println("method2执行过程4");

                System.out.println("method2执行过程5");

                System.out.println("method2执行过程6");

            }

     

        }

     

        public void test2() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method2();

                }

            }.start();

     

        }

     

     

    }

     

    我们把代码改成了类锁,也就是只把synchronized(this) 改成了synchronized (SynchronizedTest.class。

    现在我我们运行下下面的测试案例:

    method1当前线程:Thread-0

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-9

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

     

    此时我们会发现,该代码块的打印是顺序执行的,因为虽然我们调用方法的时候用的是不同的对象,但我们我们给代码块加锁用的是SynchronizedTest.class 对象,而该类对象是只有一个的,所以代码也就保持了同步

     

    同理如果我们将测试改为下面这方式,两个方法照样是同步的

     

    我就贴打印的log了,此时同一时刻,method1和method2方法只能有一个方法执行。

     

    那如过将这两个方法放在不同的类中,但是使用是同一个类锁呢?

    上代码:

    我们新建两个测试类SynchronizedTest1 和SynchronizedTest2

    public class SynchronizedTest2 {

     

        private void method2() {

            synchronized (SynchronizedTest.class) {

                System.out.println("method2当前线程:" + Thread.currentThread().getName());

                System.out.println("method2执行过程1");

                System.out.println("method2执行过程2");

                System.out.println("method2执行过程3");

                System.out.println("method2执行过程4");

                System.out.println("method2执行过程5");

                System.out.println("method2执行过程6");

            }

     

        }

     

        public void test2() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method2();

                }

            }.start();

     

        }

     

     

     

    }

    public class SynchronizedTest1 {

     

        private void method1() {

            synchronized (SynchronizedTest.class) {

                System.out.println("method1当前线程:" + Thread.currentThread().getName());

                System.out.println("method1执行过程1");

                System.out.println("method1执行过程2");

                System.out.println("method1执行过程3");

                System.out.println("method1执行过程4");

                System.out.println("method1执行过程5");

                System.out.println("method1执行过程6");

            }

     

        }

        public void test1() {

            new Thread() {

                @Override

                public void run() {

                    super.run();

                    method1();

                }

            }.start();

     

        }

     

    }

     

    然后我们做如下测试:

     

    method1当前线程:Thread-0

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-1

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-2

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-4

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-3

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-5

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-11

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-9

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-8

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-7

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-6

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-19

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-18

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-17

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-10

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-16

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method2当前线程:Thread-15

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method2当前线程:Thread-13

    method2执行过程1

    method2执行过程2

    method2执行过程3

    method2执行过程4

    method2执行过程5

    method2执行过程6

    method1当前线程:Thread-14

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

    method1当前线程:Thread-12

    method1执行过程1

    method1执行过程2

    method1执行过程3

    method1执行过程4

    method1执行过程5

    method1执行过程6

     

    通过打印,我们发现,此时两个类里的方法仍然是同步的,虽然这两个类用的是SynchronizedTest 这个类的的类锁,而不是本类的类锁,同一时间只能有一个方法执行,并且每个方法里的代码也是同步执行的。

    展开全文
  • 如果去掉synchronized,最后的结果就不是10000,因为自增操作不是原子性的 举个栗子: 假如某个时刻变量inc的值为10, 线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了; 然后线程2...

     talk is cheap,show me the code

    这里创建了包含10个线程的线程池,每个线程池对变量做1000次自增

    如果去掉synchronized,最后的结果就不是10000,因为自增操作不是原子性的

    举个栗子:

    假如某个时刻变量inc的值为10,

    线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

    然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

    然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

    那么两个线程分别进行了一次自增操作后,inc只增加了1。

    最后说明一点,操作的原子性是靠锁保证的,仅仅用volatile关键字修饰是没有用的

    因为volatile仅仅保证了变量进行操作时的可见性,也就是读取到最新的值,但是无法保证对变量的操作的原子性

    那么volatile关键字修饰有啥用呢?

    • 使用volatile关键字会强制将修改的值立即写入主存;
    • 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
    • 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
    package learn_synchronized;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class test_volatile {
    
        // 可见性
        public volatile int inc = 0;
    
        public synchronized void increase() {
            inc++;
        }
    
        public static void main(String[] args) {
            final test_volatile test = new test_volatile();
            ExecutorService addTreads = Executors.newFixedThreadPool(10);
            for (int i =1; i <= 10;i++){
                final int index=i ;
                addTreads.execute(new Runnable(){
                    @Override
                    public void run() {
                        System.out.println("\n第"+index+"个线程"+Thread.currentThread().getName());
                        for(int j = 0;j < 1000;j++) {
                            test.increase();
                        }
                    }
    
                });
            }
            addTreads.shutdown();
            // 若关闭后所有任务都已完成,则返回true。
            // 注意除非首先调用shutdown或shutdownNow,否则isTerminated永不为true。
            // 返回:若关闭后所有任务都已完成,则返回true
            while (!addTreads.isTerminated()) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("\n结束了!");
            System.out.println("\n"+test.inc);
        }
    }
    

     

    展开全文
  • 深入并发-Synchronized

    2021-03-28 22:37:38
    synchronized的使用 在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对 synchronized...(可以自己尝试将synchronizrd去掉,看看结果得到的是不是1000) public

    synchronized的使用
    在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对 synchronized进行了各种优化之后,有些情况下它就并不那么重了,Java SE 1.6中为了减少获得锁和释放锁带来的 性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。通过 synchronized关键字来修饰在inc的方法上,看看执行结果。(可以自己尝试将synchronizrd去掉,看看结果得到的是不是1000)

    public class Demo{
        private static int count=0;
        public static void inc(){
            synchronized (Demo.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++; 
            }
        }
        public static void main(String[] args) throws InterruptedException {
            for(int i=0;i<1000;i++){
                new Thread(()->Demo.inc()).start();
            }
            Thread.sleep(3000);
            System.out.println("运行结果"+count); 
        }
    }
    synchronized的三种应用方式
    synchronized有三种方式来加锁,分别是

    修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
    静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
    修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
    synchronized括号后面的对象
    synchronized扩号后面的对象是一把锁,在java中任意一个对象都可以成为锁。简单来说,我们把object比喻是一 个key,拥有这个key的线程才能执行这个方法,拿到这个key以后在执行方法过程中,这个key是随身携带的并且只有一把。如果后续的线程想访问当前方法,因为没有key所以不能访问只能在门口等着,等之前的线程把key放回去。所以,synchronized锁定的对象必须是同一个,如果是不同对象,就意味着是不同的房间的钥匙,对于访问者来说是没有任何影响的

    synchronized的字节码指令
    通过javap -v 来查看对应代码的字节码指令,对于同步块的实现使用了monitorenter和monitorexit指令,他们隐式的执行了Lock和UnLock操作,用于提供原子性保证。 monitorenter指令插入到同步代码块开始的位置、monitorexit指令插入到同步代码块结束位置,jvm需要保证每个monitorenter都有一个monitorexit对应。

    这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有 一个线程获取到由synchronized所保护对象的监视器。

    线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁;而执行 monitorexit 就是释放monitor的所有权。

    对象在内存中的布局
    在 Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。其中对象头包含对象标记和类元信息两部分,Java对象头是实现 synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。

    Mawrk Word
    Mark Word(对象标记)用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节, 也就是32bit)

    Monitor
    什么是Monitor?

    1.Monitor是一种用来实现同步的工具

    2.与每个java对象相关联,所有的 Java 对象是天生携带 monitor

    3.Monitor是实现Sychronized(内置锁)的基础

    对象的监视器(monitor)由ObjectMonitor对象实现(C++),其跟同步相关的数据结构如下:

    ObjectMonitor() {
        _count        = 0; //用来记录该对象被线程获取锁的次数
        _waiters      = 0;
        _recursions   = 0; //锁的重入次数
        _owner        = NULL; //指向持有ObjectMonitor对象的线程 
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    }
    synchronized的锁升级和获取过程
    首先来了解相关锁的概念:

    自旋锁(CAS):让不满足条件的线程等待一会看能不能获得锁,通过占用处理器的时间来避免线程切换带来的开销。自旋等待的时间或次数是有一个限度的,如果自旋超过了定义的时间仍然没有获取到锁,则该线程应该被挂起。在 JDK1.6 之后,引入了自适应自旋锁,自适应意味着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

    偏向锁:大多数情况下,锁总是由同一个线程多次获得。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,偏向锁是一个可重入的锁。如果锁对象头的Mark Word里存储着指向当前线程的偏向锁,无需重新进行CAS操作来加锁和解锁。当有其他线程尝试竞争偏向锁时,持有偏向锁的线程(不处于活动状态)才会释放锁。偏向锁无法使用自旋锁优化,因为一旦有其他线程申请锁,就破坏了偏向锁的假定进而升级为轻量级锁。

    轻量级锁:减少无实际竞争情况下,使用重量级锁产生的性能消耗。JVM会现在当前线程的栈桢中创建用于存储锁记录的空间 LockRecord,将对象头中的 Mark Word 复制到 LockRecord 中并将 LockRecord 中的 Owner 指针指向锁对象。然后线程会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针,成功则当前线程获取到锁,失败则表示其他线程竞争锁当前线程则尝试使用自旋的方式获取锁。自旋获取锁失败则锁膨胀升级为重量级锁。

    重量级锁:通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实 现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。线程竞争不使用自旋,不会消耗CPU。但是线程会进入阻塞等待被其他线程被唤醒,响应时间缓慢。

    从网上找来的一张图,完美诠释了synchronized锁的升级过程。

    Synchronized 结合 Java Object 对象中的 wait,notify,notifyAll
    前面我们在讲 synchronized 的时候,发现被阻塞的线程什 么时候被唤醒,取决于获得锁的线程什么时候执行完同步代码块并且释放锁。那怎么做到显示控制呢?我们就需要借助一个信号机制:在 Object 对象中,提供了wait/notify/notifyall,可以用于控制线程的状态。

    wait/notify/notifyall 基本概念
    wait:表示持有对象锁的线程 A 准备释放对象锁权限,释放 cpu 资源并进入等待状态。

    notify:表示持有对象锁的线程 A 准备释放对象锁权限,通知 jvm 唤醒某个竞争该对象锁的线程 X。 线程 A synchronized 代码执行结束并且释放了锁之后,线程 X 直接获得对象锁权限,其他竞争线程继续等待(即使线程 X 同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的 notify ,notifyAll 被调用)。

    notifyAll:notifyall 和 notify 的区别在于,notifyAll 会唤醒所有竞争同一个对象锁的所有线程,当已经获得锁的线程 A 释放锁之后,所有被唤醒的线程都有可能获得对象锁权限。

    需要注意的是:三个方法都必须在 synchronized 同步关键字所限定的作用域中调用,否则会报错 java.lang.IllegalMonitorStateException 。意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。另外,通过同步机制来确保线程从 wait 方法返回时能够感知到 notify 线程对变量做出的修改。

    常见面试题:wait/notify/notifyall为什么需要在synchronized里面?

    1.wait 方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列, 而这些操作都和监视器是相关的,所以 wait 必须要获得一个监视器锁。

    2.对于 notify 来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里?所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

    3.每个对象可能有多个线程调用wait方法,所以需要有一个等待队列存储这些阻塞线程。这个等待队列应该与这个对象绑定,在调用wait和notify方法时也会存在线程安全问题所以需要一个锁来保证线程安全。

    wait/notify 的基本原理

    ————————————————
    版权声明:本文为CSDN博主「岁月安然」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/baidu_38083619/article/details/82527461

    展开全文
  • 疑问 volatile关键字和synchronized关键字。 synchronized可以保证原子性、有序性和...这个volatile是否可以去掉? public class SingleInstance{ // 使用 volatile 禁止 new 对象时进行指令重排 // new 对象,有三

    疑问

    volatile关键字和synchronized关键字。

    synchronized可以保证原子性、有序性和可见性。

    volatile却只能保证有序性和可见性。

    注意:这两个有序的意思是不一样的。

    那么,我们再来看一下双重校验锁实现的单例,已经使用了synchronized,为什么还需要volatile?这个volatile是否可以去掉?

    public class SingleInstance{
    
        // 使用 volatile 禁止 new 对象时进行指令重排
        // new 对象,有三条指令完成
        // 1 JVM为对象分配一块内存M在堆区
        // 2 在内存M上为对象进行初始化
        // 3 将内存M的地址复制给instance变量
        private volatile static SingleInstance instance;
    
        private SingleInstance(){
            // 反射可以直接调用构造方法,绕过private的限制
            // 这里进行避免
            // 反序列化咋搞?
            if(instance != null){
                throw new RuntimeException("single instance existed");
            }
        }
    
        public SingaleInstance getInstance(){
            // 减少锁竞争,避免过多的线程进入同步队列,进入 blocking 状态
            if(instance == null){
                // 保证只有一个线程可以实例化对象,其他线程进入同步队列blocking
                // 这个 syn 可以保证原子性和可见性
                // 而 有序性,指的是 保证线程串行进入syn 代码块内
                // 所以,此有序性无法保证 syn代码块 内部的有序性
                synchronized(SingleInstance.class){
                    // 避免重复创建对象
                    if(instance == null){
                        instance = new SingleInstance();
                    }
                }
            }
            return instance;
        }    
    }
    

    由于 synchronized 是不能保证指令重排的,所以,可能会出问题。

    new 对象的三个步骤(简化)

    1. JVM为对象分配一块内存M。
    2. 在内存M上为对象进行初始化。
    3. 将内存M的地址复制给singleton变量。

    这个步骤有两种执行顺序可以按照 ①②③或者①③②来执行。

    当我们按照①③②的顺序来执行的时候:

    我们假设有两个线程ThreadAThreadB同时来请求SingleInstance.getInstance方法:

    正常情况按照 ①②③的顺序来执行

    • ThreadA 进入到同步代码块,执行 instance = new SingleInstance() 进行对象的初始化(按照对象初始化的过程 ①②③)执行完。
    • ThreadB进入第5行判断instance不为空(已经初始化好了),直接返回instance
    • 拿到这个对象做其他的操作。

    这样看下来是不是没有啥问题。

    但是,如果对象初始化的时候按照 ①③② 的步骤我们再来看看:

    • ThreadA进入到同步代码块,执行 instance = new SingleInstance() 执行完; ①JVM为对象分配一块内存M。③将内存的地址复制给singleton变量。
    • 此时ThreadB直接进入到同步代码块,发现instance已经不为空了然后直接就跳转到12行拿到这个instance返回去执行操作去了。此时ThreadB拿到的singleton对象是个半成品对象,因为还没有为这个对象进行初始化(②还没执行)。
    • 所以, ThreadB拿到的对象去执行方法可能会有异常产生。至于为什么会这样列?《Java 并发编程实战》有提到

    有 synchronized 无 volatile 的 DCL(双重检查锁) 会出现的情况:线程可能看到引用的当前值,但对象的状态值确少失效的,这意味着线程可以看到对象处于无效或错误的状态。

    说白了也就是ThreadB是可以拿到一个引用已经有了但是内存资源还没有分配的对象。

    解决

    解决指令重排只要给 instance 加个volatile修饰就好

    synchronizedvolatile的有序性比较

    • synchronized 的有序性:是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
    • volatile的有序性:是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。

    附录

    https://blog.csdn.net/zengfanwei1990/article/details/110245035

    展开全文
  • 多线程之synchronized

    2016-04-01 00:44:19
    顾名思义:同步锁 (1)同步代码:但是锁住的不是代码,而是传入的对象,看个例子: 这个例子的主要意思: 肯定先运行t1.start(),再运行t2.start() ...如若:去掉fun2()的同步锁代码,则先输出fun2,3秒后再输出fun
  • Lock和synchronized区别: Lock能提供更多API,如trylock():返回true表示拿到锁,false表示没有拿到锁;trylock(long time, TimeUnit unit):定义超时时间,避免死锁;unlock():上锁后要在finally中调用unlock()...
  • 如果把method2的synchronized定义去掉那么结果将是: DefaultClass.method1 begin * DefaultClass.method2 run DefaultClass.method1 end 问题: 在有synchronized定义的情况下,method1执行时是lock的,...
  • 最常见的秒杀系统,解决思路就是从前端、后台服务、数据库层层去掉负载,以达到平衡 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁...
  • 在写代码的时候,即使方法加了synchronized关键字,编辑器也会把你这个方法的锁消除掉. 2.锁消除 虚拟机的运行时编译器在运行时如果检测到一些要求同步的代码上不可能发生共享数据竞争,则会去掉这些锁。 如果JVM...
  • 详说单例模式

    2019-07-29 15:28:22
    懒汉式: public class Singleton { private static Singleton instance; private Singleton (){} //不让外部... public static synchronized Singleton getInstance (){ //去掉synchronized是线程不安全的 i...
  • 1)锁消除 概念:JVM在JIT编译(即时编译)时,通过对运行上... 在这种情景下,JVM会在JIT编译时自动将append方法上的锁去掉。 2)锁粗化 概念:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,即将加
  • 常见单例模式

    2020-10-05 17:12:31
      线程安全状态,去掉synchronized则线程不安全 /** * 单例模式 * 实例对象只有一个,没有办法new,因为构造器为private修饰了,只能通过getInstance()方法来获取实例 * getInstance()返回的是一个对象的引用,...
  • public class Vs { public static int count = 0; public synchronized static void inc() { //这里延迟1毫秒,使得结果明显 ...去掉Synchronized后的结果大概是九百多 为什么会有这样的差异?
  • 首先要了解这个异常为什么会抛出,这个异常会在三种情况下抛出: 1>当前线程不含有当前对象的锁资源的时候,调用obj.wait()方法;...现在看看代码,假如你去掉synchronized(obj){}这个同步的代码块,运行的结果就会
  • 那里面的synchronized有什么用啊?notify();方法又又什么用呢。我感觉去掉这两个东西程序运行是一样的啊??publicclassResourceFile{privatestatic...这个例子里面 不是只有一个线程吗?那里面的synchronized 有...
  • 异常信息:java.lang.IllegalMonitorStateException异常信息:原因分析:解决方案: 异常信息: java.lang.IllegalMonitorStateException ...去掉方法上的synchronized关键字,在方法体内添加lock.lock()和lock.unl
  • 2021-03-28

    2021-03-28 11:27:42
    2、Hashtable中的方法是Synchronized,而HashMap方法正常情况下不是Synchronized。 3、HashMap去掉了Hashtable的contanins方法,改成了containsKey方法()和containsValue()方法。Hashtanle保留 了contsins,...
  • //可以尝试把下面的关键字synchronized去掉。public class CubbyHole { private int contents; private boolean available = false; public synchronized int get() { while (available == false) { 
  • 单例模式

    2019-10-06 15:11:25
    1、常规的懒汉式,单例 ... //这里的volatile,可以确保初始化完singleton后,第一时间同步到主存,如果去掉volatile,则可能线程1,在synchronized中初始完成,但是synchronized块还未结束,那么就不会刷新到主存中...
  • 基本的差别sleep是Thread类的方法,wait是Object类的方法sleep方法可以在任何地方使用,wait只能在synchronized方法或synchronized块中使用最主要的本质区别Thread.sleep只会让出CPU,不会释放对象锁Object.wait不仅...
  • 基本的差别sleep是Thread类的方法,wait是Object类的方法sleep方法可以在任何地方使用,wait只能在synchronized方法或synchronized块中使用最主要的本质区别Thread.sleep只会让出CPU,不会释放对象锁Object.wait不仅...
  • wait和notify/notifyAll

    2019-05-22 14:46:29
    2.需要配合synchronized关键字使用,也就是在synchronized的代码块里面使用wait,notify/notifyAll 3.wait是使线程阻塞,前提是必须要获取一把锁,调用wait方法后,会释放锁,让出CPU,进入waiting状态,让其他线程...
  • 去掉了HashEntry结构 当前的 table【(n - 1) & hash】 == null 时,采用CAS操作 当产生hash冲突时,采用synchronized关键字 内部结构和HashMap相同,仍然使用: 数组 + 链表 + 红黑树 默认sizeCtl = 16,初始
  • 最全面的单例模式

    2020-05-26 21:17:09
    总结:线程安全、懒加载、效率低。 是否推荐:可以使用,但不推荐。 注:该模式还有另一种常见写法,就是把getInstance方法上的synchronized去掉,这种方法有线程安全问题,不能使用。
  • StringBuilder,去掉synchronized,非线程安全。 一般情况下,也不会有跨线程的字符拼接要求,所以单线程环境下,建议改为使用StringBuilder。 有人测试过,单线程环境下,两个类的性能差距不大...
  • Java高并发容器

    2020-10-22 08:14:27
    1. 高并发容器脑图 我们之前就学习了Collection下面的List和Set两大类,Queue是后来(1.5)加入的接口,...之后java的开发人员意识到了这一点,于是添加了一个HashMap,HashMap不只是把Synchronized去掉,它还把其他的
  • 如果去掉71行的synchronized,会导致线程不安全 结果就是:明明已经没有票(有人抢先一步),却又被成功订走了。图见文末 下面是正确的代码: package cn.hanquan.test; import java.util.ArrayList; import ...
  • 我们在通过第三方应用,调用usbcamera时,会有个权限的弹框,如何去掉 主要是修改UsbUserSettingsManager.java 文件 public boolean hasPermission(UsbDevice device, String packageName, int uid) { synchronized...
  • getResourceAsStream

    2016-07-11 18:16:31
    SPDB_Express_Key_Path=/key/SPDB/spdb.pfx 去掉conf前面的路径 private synchronized static void initSSLContext() { if (sslCtx == null) { String keyPath = BankMessageUtil.SPDB_Express_Key_Path; ...

空空如也

空空如也

1 2 3 4 5
收藏数 99
精华内容 39
关键字:

去掉synchronized