1.线程和进程

  • 进程:一个程序,QQ.exe Music.exe 程序的集合;
  • 一个进程往往可以包含多个线程,至少包含一个!
  • Java默认有几个线程? 2 个 mian、GC
  • 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
  • 对于Java而言:Thread、Runnable、Callable

1.1 Java可以开启线程么?不可以

  • Java把线程映射到内核上去,让操作系统自己实现

  • 是通过调用本地方法实现的

    //进入new Thread().start();里面查看源码
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    //本地方法底层的C++,Java无法直接操作硬件
    private native void start0();

    查看CPU的核数

    public class TestThread {
    public static void main(String[] args) {
        //获取CPU的核数
        //CPU 密集型,IO 密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
    }

    并发编程的本质:充分利用CPU的资源

1.2 线程的状态

public enum State {
// 新生
NEW,wait/sleep 区别
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围是不同的
wait
.
sleep 可以再任何地方睡
4、是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常
3、Lock锁(重点)
传统 Synchronized
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}

2 Lock锁

2.1 使用传统的synchronized

package com.snowdong;

/**
 * 真正的多线程开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作
 * 1、属性,方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        final Ticket ticket=new Ticket();
        //@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码}
        new Thread(()->{
                for (int i=1;i<40;i++){
                    ticket.sale();
                }
        },"Thread A").start();
        new Thread(()->{
            for (int i=1;i<40;i++){
                ticket.sale();
            }
        },"Thread B").start();
        new Thread(()->{
            for (int i=1;i<40;i++){
                ticket.sale();
            }
        },"Thread C").start();
    }
}
//资源类,面向对象的编程OOP
class Ticket{
    //属性、方法
    private int ticketNum=30;
    //卖票的方式
    //synchronized 本质:队列,锁
    public synchronized void sale(){
        if(ticketNum>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum));
        }
    }
}

2.2 使用Lock接口

图片说明
图片说明

  • 公平锁:十分公平:可以先来后到
  • 非公平锁:十分不公平:可以插队 (默认)

还是买票的代码,使用Lock锁

package com.snowdong;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 真正的多线程开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作
 * 1、属性,方法
 */
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket2 ticket=new Ticket2();
        //@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码}
        new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread A").start();
        new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread B").start();
        new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread C").start();
    }
}

//Lock三部曲
//1. new ReentrantLock()
//2. lock.lock();//加锁
//3. finally=>lock.unlock();//解锁
class Ticket2{
    //属性、方法
    private int ticketNum=30;
    Lock lock = new ReentrantLock();
    public  void sale(){
        lock.lock();//加锁
        try{
            //业务代码
            if(ticketNum>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum));
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

2.3 synchronized 和 Lock 的区别

  1. Synchronized 内置的Java关键字,Lock是一个Java类
  2. Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
    • synchronized无法得知是否获取锁成功,Lock则可以通过tryLock得知加锁是否成功
  3. Synchronized 会自动释放锁,Lock必须手动释放锁。如果不释放,死锁
  4. Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会等待下去
    • Lock可以通过tryLock得知加锁是否成功,然后判断是否等待
  5. Synchronized 可重入锁,不可以中断的,非公平;Lock锁,可重入锁,可以判断锁,公平和非公平自己设置
  6. Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。

可重入锁:可以多次获取同一个锁,释放也要多次释放
锁是同步监视器,保证线程安全,是实现同步互斥的一种机制你

2.4 生产者和消费者问题

2.4.1 生产者和消费者问题的Synchronized版

package com.snowdong;

/**
 * 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒
 * 线程之间交替执行:线程A 线程B操作同一个变量,num=0
 * A执行num+1
 * B执行num-1
 */
public class ProducerAndConsumer {
    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();
                }
            }
        },"Thread A").start();
        new Thread(()->{
            for(int i=1;i<20;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread B").start();
        new Thread(()->{
            for(int i=1;i<20;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread C").start();
        new Thread(()->{
            for(int i=1;i<20;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread D").start();
    }
}
//1. 判断等待,2.进行业务,3.通知唤醒
class Data{
    private int number=0;//数字,资源类
    //+1
    public synchronized void increment() throws InterruptedException {
        if(number !=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"===>"+number);
        //通知其他线程我+1完毕了
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if(number ==0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"===>"+number);
        //通知其他线程我-1完毕了
        this.notifyAll();
    }
}

问题存在,A B C D四个线程的时候会出现错误,出现了num=2,3...的情况!虚假唤醒

解释:

  • 拿两个加法线程A、B来说,比如A先执行(num=1),执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕(num=0)并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行(num=2)。
  • 如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行

2.4.2 虚假唤醒

  • 一般而言线程调用wait()方法后,需要其他线程调用notify,notifyAll方法后,线程才会从wait方法中返回, 而虚假唤醒(spurious wakeup)是指线程通过其他方式,从wait方法中返回
  • 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
  • 1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
    图片说明
      * A thread can also wake up without being notified, interrupted, or
      * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
      * occur in practice, applications must guard against it by testing for
      * the condition that should have caused the thread to be awakened, and
      * continuing to wait if the condition is not satisfied.  In other words,
      * waits should always occur in loops, like this one:
      * <pre>
      *     synchronized (obj) {
      *         while (&lt;condition does not hold&gt;)
      *             obj.wait(timeout);
      *         ... // Perform action appropriate to condition
      *     }
      * </pre>
      * (For more information on this topic, see Section 3.2.3 in Doug Lea's
      * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
      * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
      * Language Guide" (Addison-Wesley, 2001).
      *
      * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
      * interrupted} by any thread before or while it is waiting, then an
      * {@code InterruptedException} is thrown.  This exception is not
      * thrown until the lock status of this object has been restored as
      * described above.

if 改为 while 判断,就可以避免
wait方法可以分为三个操作:
(1)释放锁并阻塞
(2)等待条件cond发生
(3)获取通知后,竞争获取锁
wait方法一定是要获取到锁后,才会返回

2.4.3 JUC版的生产者和消费者问题

图片说明
官方实现代码:
图片说明

package com.snowdong;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒
 * 线程之间交替执行:线程A 线程B操作同一个变量,num=0
 * A执行num+1
 * B执行num-1
 */
public class ProducerAndConsumer {
    public static void main(String[] args) {
        Data data=new Data();
        new Thread(()->{
            for(int i=0;i<200;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread A").start();
        new Thread(()->{
            for(int i=1;i<200;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread B").start();
        new Thread(()->{
            for(int i=1;i<200;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread C").start();
        new Thread(()->{
            for(int i=1;i<200;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"Thread D").start();
    }
}
//1. 判断等待,2.进行业务,3.通知唤醒
class Data{//数字,资源类
    private int number=0;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    //condition.await();//等待
    //condition.signalAll();//唤醒全部
    public void increment() throws InterruptedException {
        lock.lock();//加锁
        try{
            //业务代码
            while(number !=0){
                condition.await();//等待
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"===>"+number);
            //通知其他线程我+1完毕了
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //-1
    public void decrement() throws InterruptedException {
        lock.lock();//加锁
        try{
            //业务
            while(number ==0){
                condition.await();//等待
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"===>"+number);
            //通知其他线程我-1完毕了
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

得到的运行结果是线程随机的同步执行,即随机的状态。

Condition:精准的通知和唤醒线程

package com.snowdong;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLockCondition {
    public static void main(String[] args) {
        Data03 data03=new Data03();
        new Thread(()-> {
            for(int i=1;i<10;i++)
                data03.printA();
        },"Thread A").start();
        new Thread(()-> {
            for(int i=1;i<10;i++)
                data03.printB();
        },"Thread B").start();
        new Thread(()-> {
            for(int i=1;i<10;i++)
                data03.printC();
        },"Thread B").start();
    }

}
class Data03{//资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1=lock.newCondition();
    private Condition condition2=lock.newCondition();
    private Condition condition3=lock.newCondition();
    private int number=1;//1A 2B 3C
    public void printA(){
        lock.lock();
        try{
            //业务代码:先判断-》执行-》通知
            while(number!=1)
                condition1.await();//等待条件condition1这个条件被唤醒
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA");
            //唤醒,唤醒指定的人,B
            number=2;
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try{
            //业务代码:先判断-》执行-》通知
            while(number!=2)
                condition2.await();//等待条件condition2这个条件被唤醒
            System.out.println(Thread.currentThread().getName()+"=>BBBBBBB");
            //唤醒,唤醒指定的人,C
            number=3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try{
            //业务代码:先判断-》执行-》通知
            while(number!=3)
                condition3.await();//等待条件condition3这个条件被唤醒
            System.out.println(Thread.currentThread().getName()+"=>CCCCCCCC");
            //唤醒,唤醒指定的人,A
            number=1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

3. 8锁现象

3.1 同一个对象两个synchronized方法

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 8锁就是关于锁的8个问题,
 * 1、标准情况下,两个线程那个先打印,发信息 or 打电话
 * 2、sendSms延迟了4秒,谁先打印呢
 */
public class TestLock01 {
    public static void main(String[] args) {
        Phone phone=new Phone();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"Thread A").start();

        new Thread(()->{
            phone.call();
        },"Thread B").start();
    }
}
class Phone{
    //synchronized 锁的对象是方法的调用者!
    //两个方法用的同一个锁,谁先拿到谁就执行
    public synchronized void sendSms() {
        /*try {
            //发送信息的方法,先给它睡眠4秒
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        System.out.println("发信息");
    }
    public  synchronized void call(){
        System.out.println("打电话");
    }
}

运行结果:(在sendSms方法中加不加延迟4秒,都是同一个结果)

发信息
打电话

原因解释:synchronized 锁的对象是方法的调用者。

  • 当main主线程中依次执行时,线程A先拿到sendSms方法的调用者phone对象的锁,然后进入延迟;同时线程B去执行phone.call()方法时,发现对象phone已上锁,则进入phone的等待队列中,等待别的线程解锁。

3.2 同一个对象操作synchronized方法和普通方法

代码如下

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 3、增加一个普通方法后!先执行发短信还是Hello?
 *
 */
public class TestLock02 {
    public static void main(String[] args) {
        Phone2 phone=new Phone2();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"Thread A").start();
        try {
            //发送信息的方法,先给它睡眠2秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.hello();
        },"Thread " +
                "Thread C").start();
    }
}
class Phone2{
    //synchronized 锁的对象是方法的调用者!
    //两个方法用的同一个锁,谁先拿到谁就执行
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public  synchronized void call(){
        System.out.println("打电话");
    }
    //这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

运行结果:(在sendSms方法中加不加延迟4秒,都测试)

发现synchronized方法和同步方法没有锁的联系,随机打印(需要加延迟才能出现所有情况)

原因解释:synchronized同步方***锁调用对象,普通方法不需要同步,直接主线程运行就行。

3.3 两个对象操作synchronized方法

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 4、两个对象,两个同步方法,发短信还是打电话?
 *
 */
public class TestLock02 {
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁!
        Phone2 phone1=new Phone2();
        Phone2 phone2=new Phone2();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"Thread A").start();
        new Thread(()->{
            phone2.call();
        },"Thread B").start();
    }
}
class Phone2{
    //synchronized 锁的对象是方法的调用者!
    //两个方法用的同一个锁,谁先拿到谁就执行
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public  synchronized void call(){
        try {
            //发送信息的方法,先给它睡眠4秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
    //这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

运行结果:(在两个方法中都加延迟,测出所有情况)

随机打印

原因解释:synchronized同步方***锁调用对象,两个线程中两个对象调用同步方法时会获取各自调用对象的锁,两把锁之间不关联。

3.4 同一个类的两个静态的同步方法,只有一个对象

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 5、增加两个静态的同步方法,只有一个对象,先打印那个呢
 */
public class TestLock03 {
    public static void main(String[] args) {
        Phone phone1=new Phone();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"Thread A").start();

        new Thread(()->{
            phone1.call();
        },"Thread B").start();
    }
}
class Phone{
    //synchronized 锁的对象是方法的调用者!
    //static 静态方法,类已加载就有,锁的是Class
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

运行结果:(在sendSms方法中加上延迟)

发信息
打电话

原因解释:静态的同步方***锁住调用对象的Class

  • 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone类的Class的锁,则线程B也是同样调用静态的同步方法因拿不到Class锁陷入等待。

3.5 同一个类的两个对象,类有两个静态的同步方法

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 6、两个对象,增加两个静态的同步方法对比
 */
public class TestLock03 {
    public static void main(String[] args) {
        //两个对象的Class类模板只有一个,static,锁的是Class
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"Thread A").start();

        new Thread(()->{
            phone2.call();
        },"Thread B").start();
    }
}
class Phone{
    //synchronized 锁的对象是方法的调用者!
    //static 静态方法,类已加载就有,锁的是Class
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

运行结果:同上

发信息
打电话

原因解释:静态的同步方***锁住调用对象的Class

  • 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone1类的Class的锁,因为phone1和phone2都是类Phone的实例,所以其对应的Class类模板是同一个,则当线程B通过对象phone2调用类的静态的同步方法,因拿不到Class锁陷入等待。
  • 推知:可能跟类的静态方法加载(在类实例化之前就加载了),存储位置(类静态方法跟对象实例的普通方法,不知道存储位置是不是都在方法区)

3.6 一个对象,一个静态的同步方法,一个普通同步方法

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 7、两个对象,增加两个静态的同步方法对比
 */
public class TestLock03 {
    public static void main(String[] args) {
        //两个对象的Class类模板只有一个,static,锁的是Class
        Phone phone1=new Phone();
        //线程A 通过phone1调用类的静态同步方法
        new Thread(()->{
            phone1.sendSms();
        },"Thread A").start();
/*        //捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        //线程B 通过phone1调用对象的普通同步方法
        new Thread(()->{
            phone1.call();
        },"Thread B").start();
    }
}
class Phone{
    //synchronized 锁的对象是方法的调用者!
    //static 静态方法,类已加载就有,锁的是Class
    public static synchronized void sendSms() {
        try {
            //发送信息的方法,先给它睡眠4秒
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

运行结果:是随机的
解释:普通同步方法锁的是调用者对象,静态同步方锁的是Class对象。两个是锁的是不同的对象,两把锁。

3.7 两个对象,一个静态的同步方法,一个普通同步方法

代码如下:

package com.snowdong;

import java.util.concurrent.TimeUnit;

/**
 * 8、两个对象,一个静态的同步方法,一个普通的同步方法
 */
public class TestLock03 {
    public static void main(String[] args) {
        //两个对象的Class类模板只有一个,static,锁的是Class
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"Thread A").start();
/*        //捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        new Thread(()->{
            phone2.call();
        },"Thread B").start();
    }
}
class Phone{
    //synchronized 锁的对象是方法的调用者!
    //static 静态方法,类已加载就有,锁的是Class
    public static synchronized void sendSms() {
        try {
            //发送信息的方法,先给它睡眠4秒
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

运行结果:随机结果
解释:原理同上。

4. 集合类不安全

4.1 List不安全

package com.snowdong;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//线程不安全指的是集合内部的多个方法是不能保证原子性操作的,(同步操作?)
public class TestList {
    public static void main(String[] args) {
        //ArrayList集合在多线程下会出现java.util.ConcurrentModificationException 并发修改异常
        //List<String> list=new ArrayList<>();
        /**解决方案
         * List是可重复有序集合
         * 1、List<String> list=new Vector<>();
         * 2、List<String> list= Collections.synchronizedList(new ArrayList<>());
         * 3、List<String> list=new CopyOnWriteArrayList<>();
         */
        //写入时复制,COW计算机程序设计领域的一种优化策略
        //在多个线程调用时,list读取无误,写入时有可能会覆盖以前新add的值,导致错
        //读写分离,即先复制一份,写的时候判断现在值跟复制值是否一致,一致则写入。
        //源码中CopyOnWriteArrayList().add()使用了Lock锁
        List<String> list=new CopyOnWriteArrayList<>();

        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));

                System.out.println(list);
            }).start();
        }
    }
}

4.2 Set不安全

Set是不可重复,无序集合

package com.snowdong;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
 * 同理可证 : ConcurrentModificationException
 * //1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
 */
public class TestSet {
    public static void main(String[] args) {
        // Set<String> set = new HashSet<>();//不安全
        //使用Collections工具类
        // Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <=30 ; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

hashSet的底层实现:

    public HashSet() {
        map = new HashMap<>();
    }
    //add set 本质就是map key是无法重复的
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    private static final Object PRESENT = new Object();//固定值

4.3 Map不安全

Map基本要素:初始容量,最大容量,加载因子

package com.snowdong;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class TestMap {
    public static void main(String[] args) {
       /* //出现ConcurrentModificationException,默认new HashMap<>(16,0.75);
        Map<String,String> map=new HashMap<>();*/
       //使用Collections工具类
        Map<String,String> map= Collections.synchronizedMap(new HashMap<>());
        //使用ConcurrentHashMap
        //Map<String,String> map=new ConcurrentHashMap<>();
        for(int i=1;i<=30;i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

待定专题补充ConcurrentHashMap的原理

4.4 Callable

  • 可以有返回值
  • 可以抛出异常
  • 方法不同,run()/call()

具体实现方式代码如下:

  package com.snowdong;

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

 public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启的方式
        // new Thread(new Runnable()).start();
        // new Thread(new FutureTask<V>()).start();//其中futureTask是Runnable接口的实现类
        // new Thread(new FutureTask<V>(new Callable())).start();
        MyThread myThread=new MyThread();
        //FutureTask构造函数接受Callable接口
        FutureTask futureTask=new FutureTask(myThread);//适配类
        new Thread(futureTask,"Thread A").start();
        new Thread(futureTask,"Thread B").start();//结果会被缓存,效率高

        //这个get方法可能会产生阻塞,把它放到最后
        //或者使用异步通信来处理
        Integer integer= (Integer) futureTask.get();
        System.out.println(integer);
    }
}
class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" 调用了Call");
        return 9527;
    }
}

具体来说:

  • 线程中任务执行,同一个任务执行会缓存结果
  • 结果可能需要等待,会阻塞

5 常用的辅助类

5.1 CountDownLatch减法器

图片说明

package com.snowdong;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestCountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        //设置总数为6的计数器,必须要执行任务的时候,在使用
        CountDownLatch countDownLatch=new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" go out");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }
        //注释或者不注释下行,观看运行结果
        countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println("Close door");
    }
}

说明:

  • countDownLatch.countDown();//数量-1
  • countDownLatch.await();//等待计数器归零,然后再向下执行
  • 每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续
    执行!

5.2 CyclicBarrier加法器

图片说明
可以简单理解为:加法计算器

package com.snowdong;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.w3c.dom.ls.LSOutput;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class TestCyclicBarrier {
    public static void main(String[] args) {
        /**
         * 一个案例说明:集齐7颗龙珠召唤神龙
         */
        //召唤神龙的线程
        CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });

        for (int i = 0; i <7 ; i++) {
            final int temp=i;
            //lambda能操作到i么?
            // 不能,i是for循环类的局部变量,lambda里是局部内部内了,需传参数
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();

        }
    }
}

说明:CyclicBarrier 的构造函数,在指定数目线程await发生后,执行Runnable接口实例

    /**
     * Creates a new {@code CyclicBarrier} that will trip when the
     * given number of parties (threads) are waiting upon it, and which
     * will execute the given barrier action when the barrier is tripped,
     * performed by the last thread entering the barrier.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @param barrierAction the command to execute when the barrier is
     *        tripped, or {@code null} if there is no action
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

5.3 Semaphore信号量

图片说明
抢车位的案例代码:

package com.snowdong;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class TestSemaphore {
    public static void main(String[] args) {
        //线程数量:停车位,限流.定义了一个3组信号量(许可证)
        Semaphore semaphore=new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                //获取许可证
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到了车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }).start();

        }
    }
}

运行结果:

Thread-0抢到了车位
Thread-1抢到了车位
Thread-2抢到了车位
Thread-2离开了车位
Thread-1离开了车位
Thread-0离开了车位
Thread-4抢到了车位
Thread-3抢到了车位
Thread-5抢到了车位
Thread-3离开了车位
Thread-5离开了车位
Thread-4离开了车位

说明:

  • semaphore.acquire();请求获得许可证。假设如果已满,则等待,直到被释放
  • semaphore.release();释放许可证。会将当前的许可证(信号量)+1,然后唤醒等待的线程
  • 主要用于,多个共享资源互斥的使用!并发限流,控制最大的线程数