【Java多线程JUC入门详解01】:Lock锁、集合的线程安全问题、生产者消费者问题

文章目录

  • synchronized锁
  • Lock锁
    • 与synchronized的区别
  • Lock的生产者和消费者问题
    • 全部唤醒
    • 唤醒指定线程
  • 关于锁的问题
    • 锁的是谁
    • 如果锁修饰静态方法
  • 集合的线程安全问题
    • CopyOnWriteArrayList
    • CopyOnWriteArraySet
    • ConcurrentHashMap
  • Callable实现多线程

JUC: java.util .concurrent工具包的简称:本篇即为此工具类的入门使用博客

公平锁:先来后到

非公平锁:会根据运行时间、级别进行分配

synchronized锁

package org.example;

/**
 * @author sshdg
 */
public class SynchronizedDemo {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                saleTicket.sale();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                saleTicket.sale();
            }
        }, "B").start();

    }
}
class SaleTicket {
    private int number = 100;
    public synchronized void sale(){
        System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"张票,还剩"+number);
    }

}

Lock锁

Lock是juc包下的锁,与synchronized同步锁有一些区别,功能更加强大

package org.example;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/4 20:03
 */
public class LockDemo {
    public static void main(String[] args) {
        SaleTicket2 saleTicket = new SaleTicket2();
        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                saleTicket.sale();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 50; i++) {
                saleTicket.sale();
            }
        }, "B").start();

    }
}
class SaleTicket2 {
    private int number = 100;
    Lock lock = new ReentrantLock();
    public  void sale() {

        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"张票,还剩"+number);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
}

与synchronized的区别

  • synchronized java关键字 ; Lock是一个类
  • synchronized 无法判断获取锁的状态; Lock 可以判断是否获取到了锁
  • synchronized 会自动释放锁; Lock必须手动释放锁,否则会死锁
  • synchronized 线程1(获得锁,阻塞) - 线程2(等待,一直等); Lock 不一定会一直等待下去
  • synchronized 可重入锁,不可以中断,非公平锁; Lock 可重入锁,可以判断锁,非公平(默认),可以自己设置
  • synchronized 适合少量代码的同步问题 ; Lock 适合锁大量代码的同步问题

Lock的生产者和消费者问题

全部唤醒

package org.example.pc;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/4 21:22
 */
public class ProducerConsumer {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                date.increment();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                date.decrement();
            }
        }, "B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                date.increment();
            }
        }, "C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                date.decrement();
            }
        }, "D").start();
    }
}
class Date {
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    /**
     * 生产
     */
    public void increment(){
        lock.lock();
        try {
            while (number > 0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+":number->"+number);
            //通知其他线程生产完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 消费
     */
    public void decrement(){
        lock.lock();
        try {
            while (number == 0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+":number->"+number);
            //通知其他线程消费完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

唤醒指定线程

package org.example.pc;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/5 11:12
 */
public class A {
    public static void main(String[] args) {
        Date2 date2 = new Date2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                date2.printA();
            }

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

        }, "C").start();

    }


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

    public void printA(){
        lock.lock();
        try {
            while (number != 1){
                //等待
                condition1.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName()+"--> AA");
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while (number != 2){
                //等待
                condition2.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName()+"--> BB");
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while (number != 3){
                //等待
                condition3.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName()+"--> CC");
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

关于锁的问题

锁的是谁

锁的是对象,方法的调用者。一个对象一把锁。

synchronized为例,锁只有一把,谁(线程)先抢到,谁先执行

package org.example.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @Author: sshdg
 * @Date: 2020/9/5 15:24
 */
public class Test1 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        //以这个为例,大多数情况下是A线程先抢到锁,但是这里将其睡眠1s,所以B会先执行
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
                //TimeUnit.MICROSECONDS.sleep(1);睡 1微秒 也是一样,因为计算机的速度非常快
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            phone.sendSMS();
        }, "A").start();

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

}
class Phone{
    public synchronized void sendSMS(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

而如果让方法睡1秒,则先抢到的要1s后才会执行完,在此期间占用此锁,其他线程无法执行带锁的方法。但是对无锁的方法没有影响

如果锁修饰静态方法

不建议通过对象实例访问静态方法,这样写仅为测试锁

静态方法只有一个,是在类加载的时候就有的

因此如下这种情况下,还是会先睡4s后打印发短信,然后执行打电话。

package org.example.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @Author: sshdg
 * @Date: 2020/9/5 15:24
 */
public class Test1 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone.sendSMS();
        }, "A").start();

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

}
class Phone{
    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("打电话");
    }
}

另外:静态方法的锁和普通方法的锁不是同一个。

集合的线程安全问题

CopyOnWriteArrayList

package org.example.unsafe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @Author: sshdg
 * @Date: 2020/9/5 19:52
 */
public class ListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            }, Integer.toString(i)).start();
        }
    }
}

如上代码,很容易会出现java.util.ConcurrentModificationException并发修改异常,ArrayList不是线程安全的。

解决方法

  • Vector是线程安全的,可以将ArrayList替换成Vector
  • List list = Collections.synchronizedList(new ArrayList<>());
  • List list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList源码:

其实现原理是,复制一个数组向其中添加元素,然后将复制的新数组赋值给CopyOnWriteArrayList,这样间接向CopyOnWriteArrayList中插入了数据,且使用Lock锁保证了线程安全

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

CopyOnWriteArraySet

同理,set也会有线程不安全的问题,可以使用CopyOnWriteArraySet解决,也可以使用Collections工具类来解决

package org.example.unsafe;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/5 20:38
 */
public class SetTest {
    public static void main(String[] args) {
//        Set set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(Thread.currentThread().getName()+"->"+set);
            }, Integer.toString(i)).start();
        }
    }
}

ConcurrentHashMap

原理和上面两种不同

package org.example.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @Author: sshdg
 * @Date: 2020/9/5 21:38
 */
public class MapTest {
    public static void main(String[] args) {

//        Map map = new HashMap<>();
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(Thread.currentThread().getName()+"->"+map);
            }, Integer.toString(i)).start();
        }
    }
}

Callable实现多线程

package org.example.callable;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/5 21:51
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask<Integer> futureTask = new FutureTask<>(myThread);
        new Thread(futureTask).start();
        // get()会阻塞,如果call()方法有耗时的操作,会一直在这等着,因此常放在最后一行,或使用ajax的方式,让其在后台慢慢加载
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}
class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

另:call方法执行的结果是会被缓存的,也就是说:如下代码只会输出一行call()

new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();

你可能感兴趣的:(JAVA,多线程,java,并发编程)