[会写代码的健身爱好者成长史]之多线程juc

1.什么是juc

JUC是java下面的一个包,全称java.util-concurrent,是java1.5发布的一个并发编程的工具包

2.进程和线程

 进程:指在系统中正在运行的一个应用程序,程序一旦运行就行一个进程

 线程:是操作系统能够进行运算调度的最小单位,被包含在进程中,是进程的实际运作单位

3. wait和sleep的区别

1.wait属于object类,sleep属于Tread类

2.sleep不会释放锁,它也不会占用锁,wait会释放锁

3.wait只能在同步方法或同步块中使用,而sleep可以在任何地方使用

 4.并发和并行的区别

并发:同一时刻多个线程同时访问同一个资源。例如:春运抢票,电商秒杀

并行:多项工作同时执行,然后在汇总

 5.用户线程和守护线程

如果jvm中还有用户线程,那么jvm就不会退出,如果jvm中有守护线程没有用户线程,那么jvm就会退出,总之,守护线程依赖于用户线程,用户线程退出了,守护线程也会退出,用户线程是独立的,不会因为其他线程的退出而退出

 6.lock和synchronized

lock和synchronized都是可重入锁

synchronized是java的关键字,lock是一个类,通过这个类可以实现同步访问

lock和synchronized最大的区别就是,synchronized不需要用户手动释放锁,而lock则需要用户手动释放锁,否则可能会出现死锁的情况 

 synchronized如果是普通方法,锁的就是实例对象this

 如果是静态方法,锁的就是class对象

如果是同步方法,锁的就是synchronized括号里面的配置对象

 synchronized例子

/**
 * synchronized卖票例子
 */
class Ticket {
    //总共票数
    private Integer number = 30;

    public synchronized void piao(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+":卖出"+number--+"还剩:"+number);
        }
    }
}

public class Tread {
    //创建3个线程(卖票员)
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.piao();

            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.piao();

            }
        },"BB").start();

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

}

lock 例子

/**
 * lock卖票例子
 */
class Ticket {
    //总共票数
    private Integer number = 30;

    //创建一个可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    public void piao(){

        //lock需要手动上锁
        lock.lock();

        //用try()防止出现异常
        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+":卖出"+number--+"还剩:"+number);
            }
        //finally无论有无异常都会执行下面的代码
        }finally {
            //lock需要手动释放锁
            lock.unlock();
        }
    }
}

public class Tread {
    //创建3个线程(卖票员)
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.piao();

            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.piao();

            }
        },"BB").start();

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

}

调用start方法,线程是否会创建?

答:不一定,可能创建了,可能没创建,也可能一会创建,start方法源码里面调用了一个叫start0方法,start0方法上有个native,这就代表是否创建线程取决于操作系统,如果操作系统空闲会马上创建,如果很忙,则会等一会给你创建

 线程之间的通信小例子

 2个线程,一个线程对number变量进行+1操作,一个线程对number变量进行-1操作

/**
 * 实现线程之间的通信小demo
 * 2个线程,一个线程对number变量进行+1操作,一个线程对number变量进行-1操作
 */
class Ticket {
    //number变量
    private Integer number = 0;

    //+1
    public synchronized void incr() throws InterruptedException {
        //如果number不等于0,则让它等待
        if (number!=0){
            this.wait();
        }
        //代码如果执行到这里就代表number == 0 ,等于0就+1
        number++;
        System.out.println(Thread.currentThread().getName()+":"+number);
        //+1 之后就要 -1
        this.notifyAll();
    }

    //-1
    public synchronized void decr() throws InterruptedException {
        //如果number 等于0,则让它等待
        if (number!=1){
            this.wait();
        }
        //代码如果执行到这里就代表number != 0 ,不等于0就-1
        number--;
        System.out.println(Thread.currentThread().getName()+"::"+number);
        //-1 之后就要 +1
        //wait()会让线程挂起,直到通知到它继续执行!挂起的线程会存放到等待队列中,按照wait的先后顺序存放。
        //notify()通知等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有线程
        this.notifyAll();
    }
}

public class Tread {
    //创建2个线程
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    ticket.incr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AA").start();

        new Thread(() -> {
            try {
                for (int j = 0; j < 10; j++) {
                    ticket.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

}

synchronized 实现线程通信

2个线程进行没问题,如果4个线程就会出现虚假唤醒的问题this.wait(),从而导致数据异常,解决方法就是把唤醒操作放到循环里面

 lock实现线程通信

/**
 * lock接口实现线程之间的通信小demo
 * 2个线程,一个线程对number变量进行+1操作,一个线程对number变量进行-1操作
 */
class Ticket {
    //number变量
    private Integer number = 0;

    private final Lock lock = new ReentrantLock();

    private Condition condition= lock.newCondition();

    //+1
    public void incr() throws InterruptedException {

        try {
            //上锁
            lock.lock();
            //如果number不等于0,则让它等待
            while (number!=0){
                condition.await();
            }
            //代码如果执行到这里就代表number == 0 ,等于0就+1
            number++;
            System.out.println(Thread.currentThread().getName()+":"+number);
            //+1 之后就要 -1
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }


    }

    //-1
    public synchronized void decr() throws InterruptedException {
        //如果number 等于0,则让它等待
        try {
            lock.lock();
            //防止虚假唤醒,将判断条件写到while循环里面
            while (number!=1){
                condition.await();
            }
            //代码如果执行到这里就代表number != 0 ,不等于0就-1
            number--;
            System.out.println(Thread.currentThread().getName()+"::"+number);
            //-1 之后就要 +1
            //wait()会让线程挂起,直到通知到它继续执行!挂起的线程会存放到等待队列中,按照wait的先后顺序存放。
            //notify()通知等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有线程
            //condition.signalAll()和this.notifyAll()实现的效果一样,通知等待队列的所有线程
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }
}

public class Tread {
    //创建2个线程
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    ticket.incr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AA").start();

        new Thread(() -> {
            try {
                for (int j = 0; j < 10; j++) {
                    ticket.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BB").start();

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

        new Thread(() -> {
            try {
                for (int j = 0; j < 10; j++) {
                    ticket.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"DD").start();
    }

}

lock接口实现线程之间的定制化通信 小demo
AA线程打印5次,BB线程打印10次

/**
 * lock接口实现线程之间的定制化通信小demo
 * AA线程打印5次,BB线程打印10次
 */
class Ticket {
    //flag变量
    private Integer flag = 1;
    
    private final Lock lock = new ReentrantLock();

    private Condition condition1= lock.newCondition();
    private Condition condition2= lock.newCondition();


    public void print5(Integer loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //如果flag不等于1,则让它等待
            while (flag!=1){
                condition1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i+"轮询:"+loop);
            }
            flag = 2 ;
            condition2.signal();
        }finally {
            //解锁
            lock.unlock();
        }
    }


    public  void print10(Integer loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //如果flag不等于2,则让它等待
            while (flag!=2){
                condition2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i+"轮询:"+loop);
            }
            flag = 1 ;
            condition1.signal();
        }finally {
            //解锁
            lock.unlock();
        }

    }

}

public class Tread {
    //创建2个线程
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    ticket.print5(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AA").start();

        new Thread(() -> {
            try {
                for (int j = 0; j < 10; j++) {
                    ticket.print10(j);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

}

JUC包下也有对线程不安全的集合的一些处理,让其变成线程安全 

众所周之ArrayList是线程不安全的,如果将其变成安全的呢?

 1.直接new Vector();通过看源码可以得知,实现的方法就是在方法上加synchronized,实际开发中不会这样解决,因为加synchronized会影响效率

 2.第二种处理ArrayList线程不安全的方法

上述两种情况都可以解决ArrayList线程不安全的问题,但是都比较古老,实际开发用的很少,一般都用下述这种

 HashSet也是线程不安全的,如果将其变成安全?

new CopyOnWriteArraySet<>() 即可

 HashMap?

公平锁和非公平锁 

公平锁:每个线程都会执行,效率就相对较低

非公平锁:可能会出现线程饿死的情况,但是效率很高

如果实现公平锁和非公平锁? 

就拿上述lock卖票的例子

如果new ReentrantLock()里面可以传入一个Boolean值,如果为true,就是公平锁,如果是false就是非公平锁,默认是非公平锁

 可重入锁

可重入锁指的就是  一个线程可以多次获取同一把锁

比如:一个线程在执行一个带锁的方法,该方法中又调用了另一个带着相同锁的方法,该线程可以直接调用执行该方法,无需重新获得锁

 死锁

两个或者两个以上的线程在进行争夺资源的时候,造成一种相互等待的情况,没有外力干涉的情况下,无法执行下去,这种状况叫做死锁

 //实现死锁的小例子
    public static void main(String[] args) {

        Object a = new Object();
        Object b = new Object();

        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+":已经持有锁A,试图获取锁B");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+":获取锁B");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+":已经持有锁B,试图获取锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+":获取锁A");
                }
            }
        },"B").start();
    }

 由次可以看见程序并没有结束,A,B两个线程相互等待,形成死锁

 Callable接口

众所周知,实现线程的方法,除了继承Thead类,实现Runnable接口,使用线程池,还有一个就是实现Callable接口

 Callable接口和Runnable接口区别

1.Runnable接口无返回值, Callable接口有返回值,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

实现Callable接口

//实现Callable接口
class myThead2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 888;
    }
}

public class Thead {

    public static void main(String[] args) {
        
        //new一个FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(()-> {
            System.out.println(Thread.currentThread().getName()+"callable");
            return 111;
        });
        //将其放到Thead中
        new Thread(futureTask,"BB").start();
        //futureTask.get()可以获取到Callable接口的返回值
        Integer integer = futureTask.get();
    }
}

JUC下3个辅助类 

 1.CountDownLatch计数器

countDownLatch是juc包下的一个辅助类,它是一个计数器,开始可以设置一个值,每次调用countDown()方法都会减1,当调用await()方法并且数值为0的时候,才会执行下面的代码

/**
 * 用CountDownLatch实现一个小例子
 */
public class Thead {

    public static void main(String[] args) throws InterruptedException {
        //创建一个CountDownLatch
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
                //计数-1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        //等待数值为0的时候,停止等待,执行下面的代码
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"班长锁门离开");
    }

}

如果没有使用countDownLatch,班长锁门离开这句话可能就会在前面出现

2.CyclicBarrier循环栅栏

  CyclicBarrier循环栅栏,它的作用就是所有线程都等待完成之后才会继续进一步的行动

  例子:去聚餐,必须所有人到了之后才能开始吃饭;

             要想许愿,必须集齐7颗龙珠等等;

创建一个CyclicBarrier对象,参数有2个,第一个参数是int值,代表参与线程的个数,第二个就是一个Runnable接口,当参与的线程都到了,才会执行下面的lambda表达式里面的代码

/**
 * CyclicBarrier循环栅栏
 */
public class Thead {

    private static final Integer NUMBER = 7;

    public static void main(String[] args){
        //创建一个CyclicBarrier对象,参数有2个,第一个参数是int值,代表参与线程的个数
        //第二个就是一个runnable,当参与的线程都到了,才会执行下面的lambda表达式里面的代码
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
            System.out.println("---集成了7颗龙珠召唤神龙---");
        });

        for (int i = 1; i < 8; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"号龙珠");
                    //等待7次才能召唤神龙
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }

}

3.Semaphore

/**
 * Semaphore
 * 6辆汽车停到3个停车位上
 */
public class Thead {


    public static void main(String[] args){
        //3个车位
        Semaphore semaphore = new Semaphore(3);
        //创建6个线程(6辆车)
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    //抢占
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+":抢到了车位");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"------离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
        
    }
}

乐观锁和悲观锁

 1.区别

悲观锁:每次获取数据的时候,都担心数据会被修改,所以获取数据的时候都会进行加锁,确保在使用过程中数据不会被修改,完成之后在进行解锁,由于数据会被加锁,所以对该数据在进行读写操作的其他线程会进入等待状态

乐观锁:每次获取数据的时候,都不会担心会被修改,所以获取数据的时候不会进行加锁,但是每次更新数据的时候,都会判断该数据是否被其他线程修改过,如果修改过,则不进行数据更新。如果没有被其他线程进行修改的话,则可以进行数据的更新修改。因为没有加锁的缘故,其他线程可以在该期间对其进行读写操作

2.使用场景 

悲观锁:比较适合写入操作比较频繁的场所

乐观锁:比较适合读取操作比较频繁的场所

表锁和行锁 

1.区别

表锁:对数据库某张表的第一行数据进行操作,但会把整张表都上锁,别人无法对该表的数据进行操作,表锁不会发生死锁

行锁:对数据库某张表的第一行数据进行操作,但只会对该行数据进行上锁,别人除了无法对该行进行操作,对于其他行的数据可以进行操作,行锁会发生死锁的情况

 共享锁和独占锁

1.区别

独占锁:又称写锁,指该锁只能被一个线程持有,如果有其他线程想要持有,必须要等到占用该锁的线程释放掉才行。

共享锁:又称读锁,指对于某个资源,可以同时被多个线程占用

读写锁都可能发生死锁的情况

读锁死锁情况例子:

有A,B两个线程,A线程在读取数据的时候,B线程也在读取数据,那如果A线程在读取的时候想要对数据进行修改,那么A线程就要等到B线程读取完成之后才能进行修改,恰巧的是B线程在读取数据的时候想要对数据进行修改,那么B线程也要等到A线程读取完成之后,才能修改,这种情况下就会发生读锁死锁的情况。

写锁可能发生死锁的情况:

        假设有A,B两个线程,
        A线程对数据1进行写操作,因为写锁的特性,其他线程是在线程A没有释放写锁的情况下,都无法进行操作
        B线程对数据2进行写操作,在其没有释放锁的时候,其他线程也无法进行操作
        如果A线程在对数据1进行写操作的时候,还想对数据2进行操作,那么线程A就要等到线程B释放锁才行
        恰巧的是线程B也想对数据1进行操作,那么它也要等待线程A释放锁,线程A,B都在等待对方释放锁,然后就会发生死锁

2.案例

/**
 *通过向map里面放数据和取数据,模拟读写锁的操作
 * 写锁是独占锁,读锁是共享锁
 */

class myCha{
    //创建一个map
    private volatile Map<String,String> map = new HashMap<>();
    //创建一个读写锁对象
    private ReadWriteLock lock =  new ReentrantReadWriteLock();

    //写操作
    public void put(String key,String value){
        //写操作 上锁
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"正在进行写操作");
            //暂停一会
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写完了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
    //读操作
    public String read(String key){
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+"正在进行读操作");
        try {
            //暂停一会
            TimeUnit.MILLISECONDS.sleep(300);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
        System.out.println(Thread.currentThread().getName()+"读完了");
        return map.get(key);
    }
}

public class Thead {

    public static void main(String[] args){
        myCha myCha = new myCha();
        //创建5个写锁的线程
        for (int i = 1; i < 6; i++) {
            final int num = i;
            new Thread(()->{
                myCha.put(num+"",num+"");
            },String.valueOf(i)).start();
        }
        //创建5个读锁的线程
        for (int i = 1; i < 6; i++) {
            final int num = i;
            new Thread(()->{
                myCha.read(num+"");
            },String.valueOf(i)).start();
        }
    }
}

 执行结果

 读写锁特性

1.在写的时候可以进行读,也称锁降级,但是读的时候不能写

 例子

/**
 * 读写锁降级的例子
 * 在写的时候可以进行读,也称锁降级,但是读的时候不能写
 */
public class Thead {

    public static void main(String[] args) {

        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();


        //写锁上锁
        writeLock.lock();
        System.out.println("写");

        //读锁上锁
        readLock.lock();
        System.out.println("读");

        //写锁释放
        writeLock.unlock();
        //读锁释放
        readLock.unlock();

    }
}

代码运行结果


 如果是反过来呢?

代码运行结果

阻塞队列

 1.概念 

线程1往队列里面放元素,线程2往里面取元素,如果线程1放元素的时候,队列满了,就会发生阻塞,同理,线程2取队列里面的元素,如果此时队列没有元素了,线程2也会发生阻塞的情况,直到队列里面有新的元素

 2.用法

首先先创建一个阻塞队列,阻塞队列里面有4组放和取的方法,每组方法都有些差异。

下面演示的是不一样的地方,正常的取和放效果都是一样的

//创建一个阻塞队列                     设置阻塞队列数组长度为3
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

 第一组(add放和remove取)

执行结果 

 

 第二组(offer和poll

 

执行结果

 第三组(put和take

 执行结果

 第4组(offer和poll)

 执行结果

 

线程池相关笔记

线程池 单独整理了一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值