美文网首页
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