美文网首页
Java学习笔记二之并发编程

Java学习笔记二之并发编程

作者: dev_winner | 来源:发表于2019-11-09 17:47 被阅读0次

    Synchronized同步关键字的作用:

    • 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果;其是最基本的互斥同步手段
    • 对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)。
    • 类锁:指synchronized修饰静态的方法或指定锁为Class对象。Java类可能有很多个对象,但只有1个Class对象。其用法有如下2种形式:
    1. synchronized 修饰 static 方法上
    2. synchronized 修饰(*.class)代码块
    • 本质:类锁即Class对象的锁。效果:类锁只能在同一时刻被一个对象拥有。

    多线程访问同步方法的7种情况(面试常考)

    • 两个线程同时访问一个对象的同步方法,则一个线程在执行时将某个对象锁定,且其他线程处于等待状态
    package com.zzw;
    
    public class SynchronizedObjectMethod implements Runnable {
    
        static SynchronizedObjectMethod synchronizedObjectMethod = new SynchronizedObjectMethod();
    
        @Override
        public void run() {
            method();
        }
    
        //普通方法加锁(加了同步修饰符,那么线程是串行执行的)锁对象默认是this,注意不能是静态方法,是普通方法!
        public synchronized void method() {
            System.out.println("我是对象锁的方法修饰符形式,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public static void main(String[] args) {
            //分配同一个Thread对象synchronizedObjectMethod,表示2个线程运行时只能占用一个资源对象synchronizedObjectMethod
            Thread t1 = new Thread(synchronizedObjectMethod);
            Thread t2 = new Thread(synchronizedObjectMethod);
            t1.start();
            t2.start();
            //直到2个线程死亡
            while(t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    2个线程同时访问一个对象的同步结果
    • 两个线程访问的是两个对象的同步方法,则2个线程并发执行,结果互不干扰。
    package com.zzw;
    
    public class SynchronizedObjectCodeBlocks1 implements Runnable {
    
        static SynchronizedObjectCodeBlocks1 instance1 =  new SynchronizedObjectCodeBlocks1();
        static SynchronizedObjectCodeBlocks1 instance2 =  new SynchronizedObjectCodeBlocks1();
    
        @Override
        public void run() {
            //用当前对象this作为锁,此代码块使得各个线程串行执行
            synchronized (this) {
                //不同的线程有不同的名字
                System.out.println("我是对象锁的代码块形式。我叫" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "运行结束。");
            }
        }
    
        public static void main(String[] args) {
            //使用不同的Runnable实例
            Thread t1 = new Thread(instance1);
            Thread t2 = new Thread(instance2);
            t1.start();
            t2.start();
            //死循环,直到这2个线程执行完毕
            while(t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    2个线程同时访问两个对象的同步结果
    • 两个线程访问的是synchronized的静态方法,这种属于类锁,同一时刻一个Class对象只能被一个线程占用。
    package com.zzw;
    
    public class SynchronizedClassStatic implements Runnable{
    
        static SynchronizedClassStatic instance1 = new SynchronizedClassStatic();
    
        static SynchronizedClassStatic instance2 = new SynchronizedClassStatic();
    
        //同步锁,也就是串行执行线程,使用static关键字修饰方法名,当线程执行该方法时,将会锁定该方法所在的类Class对象,使得另外的线程处于等待状态
        public synchronized static void method() {
            System.out.println("我是类锁的第一种形式:static形式。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        @Override
        public void run() {
            method();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance1);
            Thread t2 = new Thread(instance2);
            t1.start();
            t2.start();
            while(t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    两个线程访问synchronized修饰静态方法的同步结果
    • 同时访问同步方法与非同步方法,非同步方法不受影响,其不会因为其它方法加了synchronized修饰符而不能并发执行。
    package com.zzw;
    
    //同时访问同步方法和非同步方法
    public class SynchronizedYesAndNo implements Runnable{
    
        static SynchronizedYesAndNo instance = new SynchronizedYesAndNo();
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")) {
                method1();
            } else {
                method2();
            }
        }
    
        public synchronized void method1() {
            System.out.println("我是加锁的方法,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public void method2() {
            System.out.println("我是没加锁的方法,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
        
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while(t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    同时访问同步方法与非同步方法的测试结果
    • 访问同一个对象的不同的普通同步方法,虽然没有指定明确要锁定的对象,但本质是指定this作为同步锁,则线程将会串行执行。
    package com.zzw;
    
    public class SynchronizedDifferentMethod implements Runnable {
    
        static SynchronizedDifferentMethod instance = new SynchronizedDifferentMethod();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")) {
                method1();
            } else {
                method2();
            }
        }
        //访问同一个对象的不同的普通同步方法,将会指定当前类实例this作为同步锁,那么线程也就串行执行
        public synchronized void method1() {
            System.out.println("我是加锁的方法method1,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public synchronized void method2() {
            System.out.println("我是加锁的方法method2,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    访问同一个对象的不同的普通同步方法结果
    • 同时访问静态synchronized和非静态synchronized方法,静态加锁方法锁定的是.class对象,非静态加锁方法锁定的是对象实例本身this,两者不冲突,可以并发执行。
    package com.zzw;
    
    public class SynchronizedStaticAndNormal implements Runnable {
    
        static SynchronizedStaticAndNormal instance = new SynchronizedStaticAndNormal();
    
        @Override
        public void run() {
            //2个线程拿到不一样的锁,显然是并发执行的
            if (Thread.currentThread().getName().equals("Thread-0")) {
                method1();
            } else {
                method2();
            }
        }
        //类锁:synchronized修饰静态方法,锁定的是Class对象(.class)
        public synchronized static void method1() {
            System.out.println("我是静态加锁的方法method1,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
        //对象锁:synchronized修饰没有static关键字方法,锁定的是对象实例本身this
        public synchronized void method2() {
            System.out.println("我是非静态加锁的方法method2,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    同时访问静态加锁和非静态加锁方法结果
    • 方法抛异常后,会释放锁
    package com.zzw;
    
    public class SynchronizedException implements Runnable{
    
        static SynchronizedException instance = new SynchronizedException();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")) {
                method1();
            } else {
                method2();
            }
        }
        public synchronized void method1() {
            System.out.println("我是加锁的方法method1,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 运行时出错抛出异常后jvm会帮我们释放当前锁资源,那么线程2就会立马获得锁资源
            int i = 100 / 0; 
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
        public synchronized void method2() {
            System.out.println("我是加锁的方法method2,我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    方法抛异常后,jvm会帮我们释放锁

    面试常考并发7种情况总结:3点核心思想

    • 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);
    • 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法时,所有对象共用同一把锁(对应第2、3、4、6种情况);
    • 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)。

    synchronized的性质

    • 可重入(递归锁):指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。好处:避免死锁提升封装性粒度:(默认加锁的范围为线程)线程而非调用(用3种情况来说明和pthread得区别)
    • 不可中断:一旦这个锁已经被别的线程获得了,若当前线程还想获得,只能选择等待或者阻塞,直到别的线程释放这个锁。若别的线程永远不释放锁,那么当前线程只能永远地等待下去。

    相关文章

      网友评论

          本文标题:Java学习笔记二之并发编程

          本文链接:https://www.haomeiwen.com/subject/krdrpctx.html