Java并发编程学习系列一:线程与锁

概念

什么是线程和进程?

进程是程序的一次执行过程,是系统运行程序的基本单位,所以进程是动态的。系统运行一个程序便是一个进程从建立,运行到消亡的过程。java

在 Java 中,当咱们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。nginx

线程与进程类似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程当中能够产生多个线程。与进程不一样的是,同进程下的线程共享进程的方法区资源,但每一个线程有本身的程序计数器、虚拟机栈和本地方法栈,因此系统在产生一个线程,或是在各个线程之间作切换工做时,负担要比进程小得多,也正由于如此,线程也被称为轻量级进程。web

进程和线程的区别是什么?

  • 进程是运行中的程序,线程是进程的内部的一个执行序列;
  • 进程是资源分配的单元,线程是执行行单元;
  • 进程间切换代价大,线程间切换代价小;
  • 进程拥有资源多,线程拥有资源少;
  • 地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见;
  • 通讯:进程间通讯 IPC,线程间能够直接读写进程数据段(如全局变量)来进行通讯——须要进程同步和互斥手段的辅助,以保证数据的一致性;
  • 在多线程 OS 中,进程不是一个可执行的实体;

建立线程有几种不一样的方式?你喜欢哪种?为何?

  1. 继承 Thread 类(真正意义上的线程类),重写 run 方法,其中 Thread 是 Runnable 接口的实现。
  2. 实现 Runnable 接口,并重写里面的 run 方法。
  3. 使用 Executor 框架建立线程池。Executor 框架是 juc 里提供的线程池的实现。
  4. 实现 callable 接口,重写 call 方法,有返回值。

通常状况下使用 Runnable 接口,避免单继承的局限,一个类能够继承多个接口;适合于资源的共享。编程

注意:Java 本身开启不了线程,在 Thread 类中执行 start 方法时,本质上调用的是本地方法 start0,即执行底层的 C++代码,Java 没法直接操做硬件。多线程

归纳的解释下线程的几种可用状态。

  1. 新建( new ):新建立了一个线程对象。
  2. 可运行( runnable ):线程对象建立后,其余线程(好比 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。
  3. 运行( running ):可运行状态( runnable )的线程得到了 cpu 时间片( timeslice ) ,执行程序代码。
  4. 阻塞( block ):阻塞状态是指线程由于某种缘由放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时中止运行。直到线程进入可运行( runnable )状态,才有机会再次得到 cpu timeslice 转到运行( running )状态。阻塞的状况分三种:
  • 等待阻塞:运行( running )的线程执行 o.wait ()方法, JVM 会把该线程放入等待队列( waitting queue )中。
  • 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
  • 其余阻塞: 运行( running )的线程执行 Thread. sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程从新转入可运行( runnable )状态。
  1. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

并行和并发有什么区别?

  • 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  • 并行是在不一样实体上的多个事件,并发是在同一实体上的多个事件。
  • 在一台处理器上“同时”处理多个任务指的是并发,在多台处理器上同时处理多个任务指的是并行。如 hadoop 分布式集群。
    并发编程的目标是充分的利用处理器的每个核,以达到最高的处理性能。

首先给出结论:“并行”概念是“并发”概念的一个子集。咱们常常据说这样一个关键词“多线程并发编程”,一个拥有多个线程或者进程的并发程序,但若是没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。并发

若是某个系统支持两个或者多个动做(Action)同时存在,那么这个系统就是一个并发系统。app

若是某个系统支持两个或者多个动做同时执行,那么这个系统就是一个并行系统。框架

推荐阅读:并发与并行的区别? - Limbo的回答 - 知乎分布式

runnable 和 callable 有什么区别?

  • 实现 Callable 接口的任务线程能返回执行结果;而实现 Runnable 接口的任务线程不能返回结果;
  • Callable 接口的 call()方法容许抛出异常;而 Runnable 接口的 run()方法的异常只能在内部消化,不能继续上抛;

Lock锁

传统 synchronized

/**
 * 真正的多线程并发,公司中的开发,下降耦合性
 * 线程就是一个单独的资源类,没有任何附属的操做!
 * 1.属性、方法
 */

public class SychronizedDemo {

    public static void main(String[] args) {
        //并发,多线程操做同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        //声明线程使用lambda表达式,简化匿名内部类的书写
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"C").start();
    }
}

class Ticket{
    private int num = 30;

    public synchronized void sale(){
        if (num > 0){
            System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余:"+(--num));
        }
    }
}
复制代码

执行结果为:ide

A卖出了一张票,剩余:29
A卖出了一张票,剩余:28
A卖出了一张票,剩余:27
A卖出了一张票,剩余:26
A卖出了一张票,剩余:25
B卖出了一张票,剩余:24
B卖出了一张票,剩余:23
B卖出了一张票,剩余:22
B卖出了一张票,剩余:21
B卖出了一张票,剩余:20
B卖出了一张票,剩余:19
B卖出了一张票,剩余:18
B卖出了一张票,剩余:17
B卖出了一张票,剩余:16
B卖出了一张票,剩余:15
B卖出了一张票,剩余:14
B卖出了一张票,剩余:13
B卖出了一张票,剩余:12
B卖出了一张票,剩余:11
B卖出了一张票,剩余:10
B卖出了一张票,剩余:9
B卖出了一张票,剩余:8
B卖出了一张票,剩余:7
B卖出了一张票,剩余:6
B卖出了一张票,剩余:5
A卖出了一张票,剩余:4
A卖出了一张票,剩余:3
A卖出了一张票,剩余:2
A卖出了一张票,剩余:1
A卖出了一张票,剩余:0
复制代码

上述代码讲述的是卖票的例子,总共有30张票,如今交由3个售票员进行售票,每次只能容许一个售票员来进行售票行为,共用同一个票源。按照这样的需求,咱们首先想到的是使用 synchronized 关键字,synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized 关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

Lock锁

查看Lock锁的使用模版:

咱们仍是基于上述代码进行调整,主要就修改 Ticket 对象方法。

class Ticket{
    private int num = 30;

    Lock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try {
            if (num > 0){
                System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余:"+(--num));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码

执行结果一致。

使用 Lock 锁三步曲,一、声明锁;二、加锁;3解锁。

说说 synchronized 关键字和 Lock 类的区别

  • synchronized 是 Java 内置的关键字,Lock 是一个类;
  • synchronized 没法判断获取锁的状态,Lock 能够判断是否获取到了锁;
  • synchronized 会自动释放锁,Lock 必须手动释放锁,若是不释放,将会形成死锁;
  • synchronized 若是有多个线程,线程1得到锁执行时,其余线程只能傻傻的等待,Lock 锁不必定要等下去
  • synchronized 是可重入锁,不可中断,非公平锁,Lock 可重入锁,能够判断锁,非公平锁(能够设置,自由度更高);
  • synchronized 适合锁少许代码的同步问题,Lock 适合锁大量的代码。
    public ReentrantLock() {
        this.sync = new ReentrantLock.NonfairSync();
    }

    public ReentrantLock(boolean var1) {
        this.sync = (ReentrantLock.Sync)(var1 ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }
复制代码

ReadWriteLock

该接口容许多个线程同时作读操做,可是每次只能有一个线程来作写操做。

public class ReadWriteLockDemo {

    public static void main(String[] args) {

        Mycache mycache = new Mycache();

        for (int i = 0; i < 5; i++) {
            final int index = i;
            new Thread(()->{
                mycache.put("hresh"+index);
            },String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                mycache.get();
            },String.valueOf(i)).start();
        }
    }
}

class Mycache{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void put(String data){
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"准备写入操做");
            map.put("name",data);
            System.out.println(Thread.currentThread().getName()+"写入成功"+data);
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void get(){
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取操做");
            System.out.println(map.get("name"));
            System.out.println(Thread.currentThread().getName()+"读取成功");
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}
复制代码
0准备写入操做
0写入成功hresh0
2准备写入操做
2写入成功hresh2
3准备写入操做
3写入成功hresh3
1准备写入操做
1写入成功hresh1
4准备写入操做
4写入成功hresh4
2读取操做
hresh4
2读取成功
0读取操做
1读取操做
hresh4
1读取成功
hresh4
0读取成功
3读取操做
hresh4
3读取成功
4读取操做
hresh4
4读取成功
复制代码

若是将 ReentrantReadWriteLock 改成 ReentrantLock 实现,观察代码运行结果,能够发现使用了 ReentrantLock 的代码,每次只能有一个线程作读操做,而 ReentrantReadWriteLock 则是共享锁,能够容许多个线程来作读操做。且读写操做互斥,必须写完以后才能读取。

Callable

注意:Callable 接口支持返回执行结果,此时须要调用 FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘未来’结果;当不调用此方法时,主线程不会阻塞!

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableImpl implements Callable<String{
    private String acceptStr;
    public CallableImpl(String acceptStr){
        this.acceptStr = acceptStr;
    }

    @Override
    public String call() throws Exception {
//        int i = 1/0;
        Thread.sleep(3000);
        System.out.println("hello : " + this.acceptStr);
        return this.acceptStr + " append some chars and return it!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new CallableImpl("my callable test!");
        FutureTask<String> task = new FutureTask<>(callable);
        long startTime = System.currentTimeMillis();

        //建立线程
        new Thread(task).start();
        // 调用get()阻塞主线程,反之,线程不会阻塞
        String result = task.get();

        long endTime = System.currentTimeMillis();
        System.out.println("hello : " + result);
        System.out.println("cast : " + (endTime - startTime) / 1000 + " second!");
    }
}
复制代码

结果为:

//执行结果为:
hello : my callable test!
hello : my callable test! append some chars and return it!
cast : 3 second!

//若是注释get()方法,结果变为:
cast : 0 second!
hello : my callable test!
复制代码

当取消 call 方法中关于 int i = 1/0;的注释,程序结果变为:

从结果中能够看出,异常信息会向上抛出。

生产消费者问题

Sychronized,wait,notify

/**
 * @author hresh
 * @date 2020/2/16 21:19
 * @description
 * 线程之间的通讯问题:生产者和消费者问题
 * 传统解决方法,Sychronized,wait,notify三者结合使用
 */

public class A {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i=0;i<20;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data {
    private int num = 0;

    //判断等待,业务,通知
    public synchronized void increment() throws InterruptedException {
        //注意这里使用的是while判断,而非if判断,防止虚假唤醒
        while (num != 0){
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其余线程,我+1完毕了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其余线程,我-1完毕了
        this.notifyAll();
    }
}
复制代码

关于 wait 方法的判断,必须使用 while 条件,官方文档对此是这样描述的。

Lock,await,signal

public class LockDemo {

    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Data2 {
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void increment()  {
        lock.lock();
        try {
            while (num != 0){
                //等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其余线程,我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            while (num == 0){
                //等待
                condition.await();
            }

            num--;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其余线程,我-1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码

从结果中能够发现这种实现方式和 Synchronized 关键字效果一致,每当生产者生产完毕,都有一个消费者能够获取到来消费。此时若是有这么一个需求:消费者B获取生产者A的内容,D获取C的内容,实现精准获取,有序执行。采用 Synchronized 是没法知足该需求的,可是 Lock 锁有方法能够实现。

定义3个线程,A执行完该B执行,B执行完该C执行,以后再从A开始。相似案例:好比说在生产线中:下单-》支付-》交易-》物流

public class NewLockDemo {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printA();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printB();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printC();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;

    public void printA(){
        lock.lock();

        try {
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"AAAAA");
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();

        try {
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"BBBBBBBB");
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();

        try {
            while (num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"CCCCCCCC");
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码

经过声明3个 Condition 对象,每次调用方法,指定某个 Condition 等待,而后释放某个准确的 Condition。

8锁问题

Synchronized 锁实例对象

以下案例,phone 对象调用 sendSms 和 call 方法,先执行哪一个方法,就意味着该方法获取了 phone 对象的锁,另一个方法就必须等待锁被释放后,才能够执行。

/**
 * @author hresh
 * @date 2020/2/16 22:02
 * @description
 * 8锁,关于锁的8个问题
 * 1.标准状况下,先打印发短信仍是打电话?答:一、发短信;二、打电话
 * 2.sendSms延时3m,先打印哪一个?答:一、发短信;二、打电话
 */

public class Test1 {

    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}

class Phone{

    // Synchronized 锁的对象是方法的调用者,即new出来的对象
    //如下两个方法共用同一把锁
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码

当对象中除了同步方法外,还有一个普通方法,执行顺序又将变成什么样子?

//3.一个对象,添加一个普通方法,先打印hello仍是发短信?
public class Test2 {

    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}

class Phone2{

    //Sychronized锁的对象是方法的调用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public void hello(){
        System.out.println("hello");
    }
}
复制代码

执行结果为:

hello
发短信
复制代码

在第一个案例中,咱们说过 Synchronized 锁的对象是 phone 对象,这是针对同步方法而言,可是普通方法的调用并不须要获取锁,因此当同步方法在延时等待时,普通方法就能够正常执行。

当锁对象有两个时,分别调用一个方法,结果又是怎样?

//两个对象,分别调用发短信和打电话,先打印打电话
public class Test2 {

    public static void main(String[] args) {
        //两个对象
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
}

class Phone2{

    //Sychronized锁的对象是方法的调用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码

执行结果:

打电话
发短信
复制代码

由于有两个锁对象,因此你们各自都有一把锁,不须要等待锁释放,sendSms 方法由于要延迟等待,因此就是 call 方法先执行。

Synchronized 锁Class对象

被 static 修饰的方法为静态方法,随着类的加载而加载,只加载一次,不须要实例化对象,能够经过类直接进行调用,因此此时锁的对象是 Phone3.class。

//5.一个对象,两个方法都声明为静态的,分别调用发短信和打电话,先打印发短信
public class Test3 {

    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

    }
}

class Phone3{

    //Sychronized锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了!Class模版
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }

}
复制代码

若是咱们声明两个对象,分别调用这两个方法,结果又是怎样的呢?

//6.两个对象,一样都是静态的,先打印发短信,由于虽然对象不一样,可是锁的是同一个类模版
public class Test3 {

    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}
复制代码

结果与上面同样,都是先打印发短信。缘由在于 Phone.class 只有一份,关于它的锁谁先拿到就先执行,与 new 多少个对象无关。

Synchronized 多锁

当代码中存在多个锁时,也即上面提到的两种锁,一种是实例对象锁,一种是类对象锁。

//7.一个对象,一个静态同步,一个普通同步,先打印打电话
public class Test4 {

    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

    }
}

class Phone4{

    //静态同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码

结果中先打印打电话,缘由在于这两个方法须要拿到的锁不同,因此并不存在等待锁释放。

即便声明两个对象,一个调用静态同步方法,一个调用普通同步方法,结果与上述一致。

//8.两个对象,一个静态同步,一个普通同步,先打印打电话
public class Test4 {

    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}
复制代码

Synchronized 锁代码块

Synchronized 关键字还能够用来修饰同步代码块,在某些状况下,咱们可能只须要同步一部分代码,不必对整个方法进行同步操做,此时咱们可使用同步代码块的方式对须要同步的代码进行包裹,这样就无需对整个方法进行同步操做了,同步代码块的使用示例以下:

//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
复制代码

咱们可使用 this 对象(表明当前实例)或者当前类的 class 对象做为锁。如下案例来比较这三种方式的运行结果:

//9.一个对象,一个静态同步,一个普通同步方法,一个同步代码块,先打印打电话,最后发短信
public class Test5 {

    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

        new Thread(()->{
            phone1.test();
        },"C").start();
    }

}

class Phone5{

    //静态同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }

    public void test(){
        synchronized (this){
            System.out.println("synchronized code");
        }
    }
}
复制代码

执行结果为:

打电话
synchronized code
发短信
复制代码
相关文章
相关标签/搜索