02 JUC并发编程1

JUC

java.util.concurrent 工具包

业务:普通的线程代码 Thread

Runnable:没有返回值,效率相比 Callable 相对较低

1、进程和线程

进程:一个程序,QQ.exe、Music.exe

一个进程往往可以包含多个线程,至少包含一个

java 默认 有两个线程:main线程 gc线程(垃圾回收)

线程:开了一个进程 world ,写字,自动保存(线程负责的)

对于java:Thread、Runnable、Callable

java真的可以开启线程吗? 不能 java无法直接操作硬件

本地方法,底层的c++,java无法直接操作硬件
native 本地方法,底层的c++,java无法直接操作硬件

        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 */
            }
        }
    }

//native 本地方法,底层的c++,java无法直接操作硬件
    private native void start0();

线程有几个状态?

新生、运行、阻塞、等待、超时等待、终止

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

一般用timeutil

        TimeUnit.SECONDS.sleep(2);
        TimeUnit.DAYS.sleep(2);

wait/sleep 区别

  1. 来自不同的类

    ​ wait => Object

    ​ sleep => Threat

  2. 关于锁的释放

    wait会释放锁

    sleep 不会释放,睡觉了,抱着不放

  3. 使用的范围是不同的

    wait:要在同步代码块中

    sleep 可以在任意地方睡

  4. 是否需要捕获异常

    wait 不需要捕获异常

    sleep 必须要捕获异常

2、并发 并行

并发:多线程操作同一个资源,交替进行、

  • cpu一核,模拟出来多条线程 快速交替

并行:多个人一起行走,同时进行

  • cpu多核,多个线程可以同时执行,最高性能:线程池
//获取cpu的核数
  //CPU密集型,IO密集型   
System.out.println(Runtime.getRuntime().availableProcessors());
    

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

3、Lock锁(重点)

传统 synchronized

/**
 * 真正的多线程开发,公司中的开发,降低低耦合
 * 线程就是一个单独的资源类,没有任何附属的操作
 * **/
public class SaleTicketDemo1 {

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

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.saleTicket();
            }
        },"A").start();

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

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.saleTicket();
            }
        },"C").start();
    }

}

//资源类
class Ticket1{

    private int number = 30;

    public synchronized void saleTicket(){
        if (number > 0){
            System.out.println(Thread.currentThread().getName() + "买了第" + number-- + "票" + "还有" + number + "张票");
        }
    }
}

Lock锁

公平锁:十分公平;可以先来后到

非公平锁:十分不公平,可以插队(默认 )

Lock三部曲:

  1. Lock lock = new ReentrantLock();
  2. lock.lock();//加锁
  3. finally => lock.unlock();//解锁
public class SaleTicketDemo2 {

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

        new Thread(()->{for (int i = 0; i < 40; i++) ticket1.saleTicket();},"A").start();

        new Thread(()->{ for (int i = 0; i < 40; i++) ticket1.saleTicket(); },"B").start();

        new Thread(()->{ for (int i = 0; i < 40; i++) ticket1.saleTicket(); },"C").start();
        
    }


}
//资源类
class Ticket2 {

    private int number = 30;

    Lock lock = new ReentrantLock();


    public  void saleTicket() {

        lock.lock();//加锁
        
        try {
            //业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "买了第" + number-- + "票" + "还有" + number + "张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }

    }
}

Synchronized 和 Lock 区别

  1. Synchronized 是内置的java关键字 Lock是一个java类
  2. Synchronized 无法获取锁的状态,Lock可以判断是否获取到了锁
  3. Synchronized 会自动释放锁,Lock必须要手动释放锁!如果不释放锁 ,死锁
  4. Synchronized 线程1(获得锁,如果阻塞 ) 线程2(等待,一直等) ,Lock锁就不一定会等待下去
  5. Synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断,非公平锁(可设置)
  6. Synchronized 适合锁少量的代码同步问题;Lock 适合锁大量的同步代码

4、消费者和生产者

线程之间的通信问题! 等待唤醒,通知唤醒

线程交替执行 A B 操作同一个变量 num = 0

A num + 1

B num - 1

public class A {
    public static void main(String[] args) {

        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i <10 ;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();



    }
}

//判断等待、业务、通知
class Data{

    private int number = 0;

    public synchronized void increment() throws InterruptedException {

        if (number != 0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>"+number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {

        if (number == 0){
            //等待
            this.wait();
        }
        number--;
        //通知其他线程,我-1完毕了
        System.out.println(Thread.currentThread().getName() + "=>"+number);

        this.notifyAll();

    }

}

问题存在,A B C D 4个线程! 虚假唤醒

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒。等待应该总是出现在循环中

当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用的唤醒;

if改为while判断

  1. if判断流水线状态为空时,线程被阻塞,这时if判断就完成了,线程被唤醒后直接执行线程剩余操作
  2. while判断流水线状态为空时,线程被阻塞,这时的while循环没有完成,线程被唤醒后会先进行while判断

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

public class A {
    public static void main(String[] args) {

        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i <10 ;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.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

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

         
    }
}

//判断等待、业务、通知
class Data{

    private int number = 0;

    public synchronized void increment() throws InterruptedException {

        while (number != 0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>"+number);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {

        while (number == 0){
            //等待
            this.wait();
        }
        number--;
        //通知其他线程,我-1完毕了
        System.out.println(Thread.currentThread().getName() + "=>"+number);

        this.notifyAll();

    }

}

juc版的生产者和消费者问题

代码实现

public class A {
    public static void main(String[] args) {

        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i <10 ;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.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

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


    }
}

//判断等待、业务、通知
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();
        }


    }

    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 {
        }

    }

}

Condition 优势 精准的通知和唤醒线程

public class C {

    public static void main(String[] args) {
        Data1 data1 = new Data1();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.printA();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.printB();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data1.printC();
            }
        },"C").start();

    }

}


class Data1{

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;

    public void printA() {
        lock.lock();
        try {
            //业务,判断->执行->通知
            while (number != 1) {
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAA");
            //唤醒指定的人:B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

        public void printB(){
            lock.lock();
            try {
                while (number != 2){
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>BBBBB");
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    public void printC(){
        lock.lock();
        try {
            while (number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>cccccc");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

5、8锁

8锁就是关于锁的8个问题

  1. 标准情况下,两个线程先打印 发短信还是打电话? 1/发短信 2/达电话

  2. send方法延迟2秒 ,两个线程先打印 发短信还是打电话? 1/发短信 2/打电话

    synchronized 锁的对象是方法的调用者 phone

    两个方法用的同一个锁,谁先拿到谁执行

public class Test1 {
    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.send();
        },"AA");

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

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


    }
}

class Phone{

    public synchronized void send(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("发短信");
    }

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

  1. 增加了一个普通方法hello()?先执行hello再执行发短信

​ 普通方法没有锁,不是同步方法,不受锁的影响

public class Test2 {

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

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

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

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


class Phone2{

    public synchronized void seed(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

    public void hello(){
        System.out.println("hello");
    }
}
  1. 两个对象,两个同步方法,发短信还是打电话? 先打电话 发短信有延迟
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
		Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone1.seed();
        },"A").start();

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

        new Thread(()->{
            phone2.call();
        },"B").start();
  1. 增加两个静态的同步方法,只有一个对象, 发短信还是打电话?发短信,打电话

static 静态方法, 类一加载就有了,锁的是Class。

phone3只有唯一的一个class对象

//将两个方法设置成static
public class Test3 {

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

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

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

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


class Phone3{

    public static synchronized void seed(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
}
  1. 两个对象,增加两个静态的同步方法,发短信还是打电话?发短信,打电话
 public static void main(String[] args) {
     //两个对象的Class类模板只有一个
        Phone3 phone1 = new Phone3();
		Phone3 phone2 = new Phone3();
     	
        new Thread(()->{
            phone1.seed();
        },"A").start();

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

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
  1. 1个静态的同步方法,1个普通的同步方法,一个对象,发短信还是打电话?打电话 发短信 两个锁,打电话不用等发短信的延迟
//将两个方法设置成static
public class Test4 {

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

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

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

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


class Phone4{
	
    //静态的同步方法 锁的是Class类模板
    public static synchronized void seed(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
 
    //普通的同步方法 锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }

}
  1. 1个静态的同步方法,1个普通的同步方法,2个对象,发短信还是打电话?打电话 发短信 两个锁,打电话不用等发短信的延迟
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

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

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

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

6、集合类不安全

list 不安全

public class ListTest {

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().subSequence(0,5));
            },String.valueOf(i));
        }
    }
}

这样会报错

java.util.ConcurrentModificationException 并发修改异常

解决方案:

  1. List<String> list = new Vector<>();

  2. List<String> list = Collections.synchronizedList(new ArrayList<>());

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

CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略

多个线程调用的时候 list ,读取的时候是固定的,写入的时候,可能会覆盖,CopyOnWrite 写入时复制,避免覆盖,造成数据问题

CopyOnWrite 比 Vector 厉害在哪儿

Vector 的add是synchronized 效率不高

CopyOnWrite 是写入时复制

set 不安全

    Set<String> set = new HashSet<>();
    for (int i = 1; i <= 100; i++){
        new Thread(()->{
            set.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(set);
        }).start();
    }

同理 java.util.ConcurrentModificationException 并发修改异常

解决方案:

  1. Set<String> set = Collections.synchronizedSet(new HashSet<>());

  2. Set<String> set = new CopyOnWriteArraySet<>();

HashSet 本质是 Map

add() 本质是 map.put key是无序的 不可重复的

HashMap 不安全

//Map<String,String> map = new HashMap<>(16,0.75);
Map<String,String> map = new HashMap<>();

       for (int i = 1; i <= 100; i++){
            new Thread(()->{                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

java.util.ConcurrentModificationException 并发修改异常

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

位运算

初始容量 1 << 4 16

static final float DEFAULT_LOAD_FACTOR = 0.75f;

加载因子 0.75

解决方案:

Map<String,String> map = new ConcurrentHashMap<>();

7、callable

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同 call()

get() 方法可能会产生阻塞! 把他放到最后

或者使用异步通信来处理

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(newFutureTask<V>()).start();
        //new Thread(newFutureTask<V>(Callable)).start();

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);

        new Thread(futureTask,'A').start();//call
        new Thread(futureTask,'B').start();//只会打印一个call,结果会被缓存,效率高
        String str = (String) futureTask.get();//获取callable的返回值 可能会产生阻塞
        System.out.println(str);//1024
    }

}


class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("call");
        return "1024 ";
    }
}

细节:

  1. 有缓存
  2. 结果可能需要等待,会阻塞
posted @   flypiggg  阅读(53)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示