juc入门篇(上)

Mr.LR2022年4月5日
大约 19 分钟

juc入门篇(上)

1、juc是什么

1.1java.util.concurrent在并发编程中使用的工具类

image-20220411161118892

1.2进程/线程

1.2.1进程/线程是什么

进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

1.2.2进程/线程例子

使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。

大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。

word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查

1.2.3线程状态

public enum State {

    NEW,//新生
    RUNNABLE,//运行
    BLOCKED,//阻塞
    WAITING,//等待 死死的等
    TIMED_WAITING,//超时等待
    TERMINATED;//终止
}

1.2.4wait/sleep区别

1、wait放开手去睡,放开手里的锁

2、sleep握紧手去睡,醒了手里还有锁

1.2.5什么是并发?什么是并行?

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:小米9今天上午10点,限量抢购 春运抢票 电商秒杀...

并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

2、Lock接口

2.1复习synchronized

多线程编程模板:线程 操作 资源类;高内聚 低耦合

2.1.1实现步骤:

  • 创建资源类
  • 资源类里面创建同步方法,同步代码块
  • 买票程序例子
//资源类
class Ticket
{
    private static int number = 30;
    public synchronized void sale(){
        if (number>0){
            number--;
            System.out.println(Thread.currentThread().getName()+" 卖出"+(number)+"还剩"+(30-number));
        }
    }
}


//线程
// 操作(资源类对外暴露的方法) 比如sale  ,
// 资源类
// ;高内聚 低耦合
public class SaleTicket {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //同时两个线程跑了15次
        new Thread(()->{//线程
           for(int i=0;i<15;i++){
               ticket.sale();//操作
           }
        },"A").start();

        new Thread(()->{
            for(int i=0;i<15;i++){
                ticket.sale();
            }
        },"B").start();

    }

}

2.2lock

2.2.1Lock是什么

锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。

image-20220411164127197

2.2.2Lock接口的实现 ReentrantLock可重入锁

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

2.2.3synchronized与Lock的区别

两者区别:

  • 1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • 2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • 3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • 5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  • 6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

3、创建线程方式

3.1继承Thread -不常用

public class SaleTicket extends Thread

java是单继承,资源宝贵,要用接口方式

3.2new Thread() -不常用

Thread t1 = new Thread(); t1.start();

3.3实现runnable接口

  • 新建类实现runnable接口
class MyThread implements Runnable//新建类实现runnable接口

new Thread(new MyThread,...)

这种方法会新增类,有更新更好的方法
  • 匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
 
    }
   }, "your thread name").start();
  • lambda表达式
new Thread(() -> {

 }, "your thread name").start();

4、线程间通信

4.1线程间通信

1、生产者+消费者 2、通知等待唤醒机制

模板:1、判断。2、干活。3、通知

4.2 synchronized实现

4.2.1代码

/**
 * synchronized 实现线程间通信
 *
 * @Author LR
 * @Date 2022/3/12 14:48
 */
public class NotifyWaitDemoOne {
    public static void main(String[] args) {
        ShareDataOne sd = new ShareDataOne();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    sd.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    sd.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
    }
}

class ShareDataOne//资源类
{
    private int number = 0;//初始值为零的一个变量

    public synchronized void increment() throws InterruptedException {
        //1判断
        if (number != 0) {
            this.wait();
        }
        //2干活
        ++number;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        //3通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1判断
        if (number == 0) {
            this.wait();
        }
        // 2干活
        --number;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3通知
        this.notifyAll();
    }
}

4.2.2换成四个线程问题

换成4个线程会导致错误,虚假唤醒

  • 原因:在java多线程判断时,不能用if,程序出事出在了判断上面, 突然有一添加的线程进到if了,突然中断了交出控制权, 没有进行验证,而是直接走下去了,加了两次,甚至多次

解决办法:

  • 解决虚假唤醒:查看API,java.lang.Object
  • 中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while

image-20220411165351249

判断改为while即可:

while (number != 0) {
       this.wait();
}

4.3Lock实现

4.3.1Condition

Condition:查看API,java.util.concurrent

image-20220411165611642

实现代码

public class NotifyWaitDemo {
    public static void main(String[] args) {
        ShareData sd = new ShareData();
        new Thread(() -> {

            for (int i = 1; i <= 10; i++) {
                try {
                    sd.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {

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

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

            for (int i = 1; i <= 10; i++) {
                try {
                    sd.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();

    }
}

class ShareData {//资源类
    private int number = 0;//初始值为零的一个变量

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

    public void increment() throws InterruptedException {

        lock.lock();
        try {
            //判断
            while (number != 0) {
                condition.await();
            }
            //干活
            ++number;
            System.out.println(Thread.currentThread().getName() + " \t " + number);
            //通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            //判断
            while (number != 1) {
                condition.await();
            }
            //干活
            --number;
            System.out.println(Thread.currentThread().getName() + " \t " + number);
            //通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

5、线程间定制化调用通信

  • 线程-调用-资源类
  • 判断-干活-通知
    • 有顺序通知,需要有标识位
    • 有一个锁Lock,3把钥匙Condition
    • 判断标志位
    • 输出线程名+第几次+第几轮
    • 修改标志位,通知下一个、
    • 代码实现
class ShareResource
{
  private int number = 1;//1:A 2:B 3:C 
  private Lock lock = new ReentrantLock();
  private Condition c1 = lock.newCondition();
  private Condition c2 = lock.newCondition();
  private Condition c3 = lock.newCondition();
 
  public void print5(int totalLoopNumber)
  {
     lock.lock();
     try 
     {
       //1 判断
       while(number != 1)
       {
          //A 就要停止
          c1.await();
       }
       //2 干活
       for (int i = 1; i <=5; i++) 
       {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 2;
       c2.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }
  public void print10(int totalLoopNumber)
  {
     lock.lock();
     try 
     {
       //1 判断
       while(number != 2)
       {
          //A 就要停止
          c2.await();
       }
       //2 干活
       for (int i = 1; i <=10; i++) 
       {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 3;
       c3.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }  
  
  public void print15(int totalLoopNumber)
  {
     lock.lock();
     try 
     {
       //1 判断
       while(number != 3)
       {
          //A 就要停止
          c3.await();
       }
       //2 干活
       for (int i = 1; i <=15; i++) 
       {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 1;
       c1.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }  
} 
/**
 * 
 * @Description: 
 * 多线程之间按顺序调用,实现A->B->C
 * 三个线程启动,要求如下:
 * 
 * AA打印5次,BB打印10次,CC打印15次
 * 接着
 * AA打印5次,BB打印10次,CC打印15次
 * ......来10轮  
 *
 */
public class ThreadOrderAccess
{
  public static void main(String[] args)
  {
     ShareResource sr = new ShareResource();
     
     new Thread(() -> {
       for (int i = 1; i <=10; i++) 
       {
          sr.print5(i);
       }
     }, "AA").start();
     new Thread(() -> {
       for (int i = 1; i <=10; i++) 
       {
          sr.print10(i);
       }
     }, "BB").start();
     new Thread(() -> {
       for (int i = 1; i <=10; i++) 
       {
          sr.print15(i);
       }
     }, "CC").start();    
  }
}

6、集合安全

6.1集合不安全

6.1.1线程不安全错误

java.util.ConcurrentModificationException

ArrayList在迭代的时候如果同时对其进行修改就会
抛出java.util.ConcurrentModificationException异常
并发修改异常

6.1.1不安全原因

List<String> list = new ArrayList<>();
for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

//看ArrayList的源码没有synchronized线程不安全
public boolean add(E e) {//
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

6.2解决办法

6.2.1Vector

List<String> list = new Vector<>();Vector的源码
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}synchronized线程安全

image-20220411170531056

6.2.2Collections

List<String> list = Collections.synchronizedList(new ArrayList<>());
Collections提供了方法synchronizedList保证list是同步线程安全的
 
那HashMapHashSet是线程安全的吗?也不是
所以有同样的线程安全方法

image-20220411170629294

6.2.3写时复制

List<String> list = new CopyOnWriteArrayList<>();

image-20220210230509299

  • 不加锁性能提升出错误,加锁数据一致性能下降
6.2.3.1举例:名单签到

image-20220411170824106

6.2.3.2CopyOnWrite理论
 
/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
 
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。
添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
6.2.3.3扩展类比
  • HashSet
Set<String> set = new HashSet<>();//线程不安全

Set<String> set = new CopyOnWriteArraySet<>();//线程安全

HashSet底层数据结构是什么?
HashMap  ?
 
但HashSet的add是放一个值,而HashMap是放K、V键值对

public HashSet() {
    map = new HashMap<>();
}
 
private static final Object PRESENT = new Object();
 
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • HashMap
Map<String,String> map = new HashMap<>();//线程不安全
 

Map<String,String> map = new ConcurrentHashMap<>();//线程安全

7、多线程锁

7.1经典8锁-规则

  • 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了, 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

    • 加个普通方法后发现和同步锁无关
    • 换成两个对象后,不是同一把锁了,情况立刻变化。
  • synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

    • 对于普通同步方法,锁是当前实例对象。
    • 对于静态同步方法,锁是当前类的Class对象。
    • 对于同步方法块,锁是Synchonized括号里配置的对象
  • 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

    • 也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
    • 可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
  • 所有的静态同步方法用的也是同一把锁——类对象本身。

    • 这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
    • 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
    • 而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

7.2经典8锁问题

7.2.1标准访问,请问先打印邮件1还是短信2?

/**
 * **1、标准访问,请问先打印邮件1还是短信2?** sendEmail
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> {phone.sendEmail();}, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {phone.sendMS();}, "B").start();
    }
}

// 手机,发短信,发邮件
class Phone {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        System.out.println("sendEmail");
    }
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

7.2.2邮件方法暂停4秒钟,请问先打印邮件还是短信?

/**
 * **2、邮件方法暂停4秒钟,请问先打印邮件还是短信?** sendEmail
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> {phone.sendEmail();}, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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


// 手机,发短信,发邮件
class Phone2 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

7.2.3新增一个普通方法hello()没有同步,请问先打印邮件还是hello

/**
 * 3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?  hello
 */
public class Test3 {

    public static void main(String[] args) {

        Phone3 phone = new Phone3();

        // 普通方法没有锁,当然先打印了
        new Thread(() -> {phone.sendEmail();}, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 一秒后执行
        new Thread(() -> {phone.hello();}, "B").start();

    }
}

class Phone3 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
    // 新增的方法没有被 synchronized 修饰,不是同步方法,所以不需要等待,其他线程用了一个把锁
    public void hello() {
        System.out.println("hello");
    }
}

7.2.4两部手机、请问先打印邮件还是短信

/**
 * **4、两部手机、请问先打印邮件还是短信?** sendMS
 */
public class Test4 {

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

        // 我们这里两个线程 但是是两个对象调用,因此不是一把锁了,synchronized锁的是this
        new Thread(() -> { // 一开始就执行了
            phone1.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone2.sendMS();
        }, "B").start();

    }

}

class Phone4 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、调用者不同,没有关系,两个方法用得不是同一个锁!
    public synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

7.2.5两个静态同步方法,同一部手机,请问先打印邮件还是短信

/*
 **5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?** sendEmail
 */
public class Test5 {
    public static void main(String[] args) {
        Phone5 phone = new Phone5();
        // static修饰,锁的是类模板,即一个对象,或者两个对象,都是同一把锁
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { // 一秒后执行
            phone.sendMS();
        }, "B").start();
    }
}
// 手机,发短信,发邮件
class Phone5 {

    // 对象    类模板可以new 多个对象!
    // Class   类模版,只有一个

    // 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }
    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

7.2.6两个静态同步方法,2部手机,请问先打印邮件还是短信?**

/** 
 **6、两个静态同步方法,2部手机,请问先打印邮件还是短信?** sendEmail
 */
public class Test6 {

    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone6 phone = new Phone6();
        Phone6 phone2 = new Phone6();
      // static修饰,锁的是类模板,即一个对象,或者两个对象,都是同一把锁
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { // 一秒后执行
            phone2.sendMS();
        }, "B").start();
    }
}

// 手机,发短信,发邮件
class Phone6 {
    // 对象    类模板可以new 多个对象!
    // Class   类模版,只有一个
    // 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
    // 同一把锁
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }
    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

7.2.7一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?

/**
 * 7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?** sendMS
 */
public class Test7 {
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone7 phone = new Phone7();
        // 虽然是一个对象,但是 锁不同,一个是类锁,一个是对象锁。
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();
        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { // 一秒后执行
            phone.sendMS();
        }, "B").start();

    }
}
// 解析:两个方法的锁不同,所以不阻塞
class Phone7{
    // CLASS
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }
    // 对象
    // 普通同步方法
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

7.2.8一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?

/**
 * **8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?**sendMS
 */
public class Test8 {
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone8 phone = new Phone8();
        Phone8 phone2 = new Phone8();
        // 两个对象 两把锁,就更不用考虑是否静态同步和普通同步了
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

        // 干扰
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> { // 一秒后执行
          phone2.sendMS();
        }, "B").start();
    }
}
class Phone8{
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }
    // 对象
    // 普通同步方法
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

8、Callable接口

8.1Callable是什么

8.1.1实现多线程的第三种方法

  • 继承Thread类
  • 实现runnable接口
  • 实现Callable接口

8.1.2Callable简介

这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。

image-20220412094320939

8.2与runnable对比

 创建新类MyThread实现runnable接口
class MyThread implements Runnable{
 @Override
 public void run() {
 
 }
}
新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
 @Override
 public Integer call() throws Exception {
  return 200;
 } 
}
 面试题:callable接口与runnable接口的区别?
 
 答:(1)是否有返回值
       (2)是否抛异常
       (3)落地方法不一样,一个是run,一个是call

8.3用法

8.3.1直接替换runnable是否可行

不可行,因为:thread类的构造方法根本没有Callable

image-20220412094458753

8.3.2FutureTask

  • 是什么

未来的任务,用它就干一件事,异步调用 main方法就像一个冰糖葫芦,一个个方法由main串起来。 但解决不了一个问题:正常调用挂起堵塞问题

image-20220412094835796

例子: (1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水, 水买回来了放桌上,我需要的时候再去get。 (2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊, FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果 (3)高考:会做的先做,不会的放在后面做

  • 原理

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成, 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。

一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成, 就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态, 然后会返回结果或者抛出异常。

只计算一次 get方法放到最后

  • 代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread implements Runnable{

    @Override
    public void run() {

    }
}
class MyThread2 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"come in callable");
        return 200;
    }
}


public class CallableDemo {


    public static void main(String[] args) throws Exception {

        //FutureTask<Integer> futureTask = new FutureTask(new MyThread2());
        FutureTask<Integer> futureTask = new FutureTask(()->{
            System.out.println(Thread.currentThread().getName()+"  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 1024;
        });
        FutureTask<Integer> futureTask2 = new FutureTask(()->{
            System.out.println(Thread.currentThread().getName()+"  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 2048;
        });

        new Thread(futureTask,"zhang3").start();
        new Thread(futureTask2,"li4").start();

        //System.out.println(futureTask.get());
        //System.out.println(futureTask2.get());
        //1、一般放在程序后面,直接获取结果
        //2、只会计算结果一次

        while(!futureTask.isDone()){
            System.out.println("***wait");
        }
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+" come over");
    }
}
 
/**
 * 
 * 
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
 
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
 
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,
就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,
然后会返回结果或者抛出异常。 
 
只计算一次
get方法放到最后
 */

参考

【并发编程_ Java阳哥- 尚硅谷】 https://www.bilibili.com/video/BV1vE411D7KE?p=38open in new window