首页 > 其他 > 详细

JUC并发编程

时间:2021-02-04 22:46:13      阅读:54      评论:0      收藏:0      [点我收藏+]

1.传统的synchronized

我们实际上去公司写线程的代码时,不能让我们的资源类去继承Thread或者实现Runnable接口,我们应该将资源类完全隔离开来,它里面就只有属性和方法。

//基本的买票例子
//真正的多线程开发,公司中的开发一定要降低耦合性,线程就是一个单独的资源类,没有任何的附属操作
//里面包含有1.属性  2.方法
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多个线程操作同一个资源类
        Ticket ticket = new Ticket();

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

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

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

//资源类 OOP编程
class Ticket{
    //属性,方法
    private int number = 50;

    //买票的方式
    //synchronize本质就是一个队列
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
        }
    }
}

   

2.Lock接口

2.1 Lock接口的实现类

  • ReentrantLock:可重入锁(常用的)
  • ReadLock:读锁
  • WriteLock:写锁

技术分享图片

 

 

 我们通过运用Lock的实现类来加锁

2.2 ReentrantLock

我们先来看一下ReentrantLock的构造器,它默认是返回一个非公平锁,这让的目录就是提高运行效率,比如说

技术分享图片

 

 

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

非公平锁:十分不公平,可以插队

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

//基本的买票例子
//真正的多线程开发,公司中的开发一定要降低耦合性,线程就是一个单独的资源类,没有任何的附属操作
//里面包含有1.属性  2.方法
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //并发:多个线程操作同一个资源类
        Ticket2 ticket = new Ticket2();

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

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

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

//资源类 OOP编程
//1.new ReentrantLock
//2.加锁
//3.解锁
class Ticket2{
    //属性,方法
    private int number = 50;

    Lock lock = new ReentrantLock();

    //买票的方式
    //synchronize本质就是一个队列
    public void sale(){
        lock.lock();//加锁
        try{
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

  

3. Lock和synchronized的区别

  1. synchronized是一个内置的java关键字,Lock是一个java类
  2. synchronized无法判断获取锁的状态,Lock可以判断是否获得锁了
  3. synchronized会自动释放锁,lock必须要自动释放锁,如果不释放,死锁
  4. synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);lock锁就不一定等待下去通过lock.tryLock()
  5. synchronized 默认是可重入锁,不可以中断的,非公平。因为它是一个关键字,这些特性都是不能更改的。Lock,可重入锁,可以判断锁,非公平(可以自己设置)技术分享图片
  6. synchronized 适合锁少量的代码同步问题,Lock适合锁大量的资源。

4. 生产者和消费者问题

4.1 synchronized 版本(下面这种只有一个生产者消费者那么就没有问题,如果有两个生产者两个消费者或者更多那么就会产生很多问题)

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 < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }

}

//等待,业务,通知
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()+"加操作");
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"减操作");
        this.notifyAll();
    }
}

  

技术分享图片

 

 这里一定要用while,if只会判断一次,等待应该总是出现在循环中。不能说判断条件中途改变了就立刻不wait了

 

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 < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

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

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

}

//等待,业务,通知
class Data{//数字 资源类
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"加操作");
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"减操作");
        this.notifyAll();
    }
}

  

4.2 JUC版本(Condition接口和Lock平级)

技术分享图片

 

 condition就是替代同步监视,替换掉我们之前用的Synchronized的版本

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

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        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 Data2{//数字 资源类
    private int number = 0;

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

    //+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0){
                condition.await();
            }
            number++;
            condition.signalAll();
            System.out.println(Thread.currentThread().getName()+"加操作");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                condition.await();
            }
            number--;
            condition.signalAll();
            System.out.println(Thread.currentThread().getName()+"减操作");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

  

那么为什么要用condition呢?

condition除了可以替换wait和notifyall更重要的一点功能使它强于sychronized就是它可以精准的通知和唤醒线程,notify()只能随机唤醒一个线程,是由线程调度器随机分配的,notifyall它默认唤醒所有线程。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.print1();
            }
        },"A").start();

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

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

class Data3{//资源类

    private int number = 1;//1A 2B 3C

    private Lock lock = new ReentrantLock();
    //创建三个不同的监视器,通过监视器来判断我们到底应该唤醒的是哪个人
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void print1(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print2(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print3(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


}

  

这种精准调用的应用:生产线:下单--》支付--》交易--》物流

5. 八锁现象

锁的是谁?

  • new 出来的对象
  • new 出来所依据的class模板

5.1 synchronized用在方法上锁的是谁?(调用方法的对象!!!)

import java.util.concurrent.TimeUnit;

/**
 * 标准情况下,是先打印发短信还是先打印打电话呢?
 * 那我们要是在这个sendSms中去加一个sleep呢,结果又会怎样呢?
 * 其实这里无论怎么变化都是先会打印发短信,再打印打电话的。
 */
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其实锁的是方法的调用者,这也就不难解释上面我们只创建了一个Phone对象
    public synchronized void sendSms() {
        System.out.println("发短信");
    }

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

 技术分享图片

 

 

 

5.2 增加一个普通方法的情况,关于主进程是否需要拿到锁中的对象呢

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

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

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

class Phone2{

    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");
    }
}

 技术分享图片

 

 

 

5.3 加了static 静态同步方法的情况

import java.util.concurrent.TimeUnit;

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();
    }
}

class Phone3{

    //static 静态方法
    // 类一加载了就有了,加了static的同步方法锁的就是方法调用者的class
    // 当然我们也知道无论有多少个对象都只有一个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("打电话");
    }
}

  技术分享图片

 

 

5.4 普通同步方法静态同步方法同时在

import java.util.concurrent.TimeUnit;

/**
 * 锁的东西都不一样
 */
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone = new Phone4();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

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

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

class Phone4 {
    //静态同步方法
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //普通同步方法
    public synchronized void call() {
        System.out.println("打电话");
    }
}

  技术分享图片

 

 6. 集合类不安全

6.1 CopyOnWriteArrayList

首先Vector是线程安全的,ArrayList是线程不安全的

技术分享图片

 

 技术分享图片

 

 

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

// java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
    public static void main(String[] args) {

        /**
         * 原本:List<String> list = new ArrayList<>();
         * 解决方法:
         * 1.将ArrayList换成Vector List<String> list = new Vector<>();
         * 2.采用集合的顶级工具类来进行一个转化 List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3.采用JUC包下的 CopyOnWrite:写入时复制(提高计算机程序设计领域的一种优化策略)List<String> list = new CopyOnWriteArrayList<>();
         * 为什么了要有这个东西呢,因为在多线程写入的时候可能出现写入覆盖的问题,在写入的时候呢我们先copy一份再写入再放回去。
         * 读写分离 (写入的时候复制一份来写)
         * CopyOnWriteArrayList 比 Vector 牛逼在那里,只要有synchronized方法效率就比较低,Vector用的是synchornized
         * CopyOnWriteArrayList 用的是Lock
         */
        List<String> list = new CopyOnWriteArrayList<>();

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

        }
    }
}

 

6.2 CopyOnWriteArraySet

技术分享图片

 

 

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetTest {
    public static void main(String[] args) {
        Set<String> set1 = new HashSet<>();
        //方法一
        Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
        //方法二
        Set<String> set3 = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

  

扩展:HashSet的底层就是一个HashMap,就是用了它的这个key,key是不重复的。

 技术分享图片

 

 HashSet.add(),其实就是将你要存的数据当作HashMap的key存进去罢了。key不会重复,set不允许有重复的值。

技术分享图片

 

PRESENT其实就是一个固定的不变的值。

 技术分享图片

 

 6.3 ConcurrentHashMap

HashMap构造方法中:

技术分享图片

 

 

技术分享图片

 

 

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

public class MapTest {
    public static void main(String[] args) {
        //map是这样用的吗? 不是,工作中不用这样的HashMap
        // 默认等价于什么? Map<String, Object> map = new HashMap<>(16,0.75);
        Map<String, Object> map = new HashMap<>();

        //方法一:
        Map<Object, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());

        //方法二:
        Map<String,Object> map1 = new ConcurrentHashMap<>();
        //加载因子,初始容量

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

  

 

JUC并发编程

原文:https://www.cnblogs.com/yaoyaoo/p/14375247.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!