Java多线程高并发,代码示例(二)

前言

项目主体源码可以从ConcurrenceBasics获得,喜欢的朋友可以点个star~。

synchronized优化,同步代码块中的语句越少越好
/**
 * synchronized优化
 * 同步代码块中的语句越少越好,比较m1和m2
 */
public class T {
    int count = 0;
    synchronized void m1(){
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务逻辑上只有下面这个语句需要sync,这时就不需要给整个方法上锁
        count++;
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    void m2(){
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁。
        //采用细粒度的锁,可以使线程争用时间变短,从而提高效率。
        synchronized (this){
            count++;
        }
        //do sth need not sync
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
锁定某个对象o,o变成另一个对象,则锁定的对象发生改变
/**
 * 锁定某个对象o,如果o的属性发生改变,不影响锁的使用。
 * 但是如果o变成另一个对象,则锁定的对象发生改变。
 * 应该避免将锁定对象的引用变成另外的对象。
 */
public class T {
    Object o = new Object();
    void m(){
        synchronized (o){
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        T t = new T();
        //启动第一个线程
        new Thread(t::m, "t1").start();

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

        //创建第二个线程
        Thread t2 = new Thread(t::m,"t2");
        //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程t2将永远都不会有执行的机会
        t.o = new Object();
        t2.start();
    }
}
不要以字符串常量作为锁定对象,可能会出现死锁
/**
 * 不要以字符串常量作为锁定对象,
 * 在下面的例子中,m1和m2其实锁定的是同一个对象,
 * 这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串"hello",
 * 但是你读不到源码,所以你在自己的代码中也锁定了"hello",这时候就有可能发生非常诡异的死锁阻塞,
 * 因为你的程序和你用到的类库不经意间使用了同一把锁。
 */
public class T {
    String s1 = "Hello";
    String s2 = "Hello";
    void m1(){
        synchronized (s1){

        }
    }

    void m2(){
        synchronized (s2){

        }
    }
}
countdownlatch基本使用
/**
 * 实现一个容器,提供两个方法,add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时,线程2给出提示并结束。
 * 解决方法一:给lists添加volatile之后,t2能够接收通知,但是t2线程的死循环很浪费cpu,如果不用死循环,该怎么做?
 * 解决方法二:可以使用wait和notify做到,wait会释放锁,而notify不会释放锁,需要注意的是,运行这种方法,必须要保证
 * t2先执行,也就是首先先让t2监听才可以,notify之后,t1必须释放锁,t2退出后也必须notify,通知t1继续执行。
 * 解决方法三:使用Latch(门闩)代替wait notify来进行通知,
 * 好处是通信方式简单,同时也可以指定等待时间,使用await和countdown方法代替wait和notify,
 * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了,
 * 这是应该考虑countdownlatch/cyclicbarrier/semaphore
 */
public class MyContainer {
    //添加volatile,使t2能够得到通知
    volatile List list = new ArrayList();
    public void add(Object o){
        list.add(o);
    }
    public int size(){
        return list.size();
    }

    public static void main(String[] args) {
        MyContainer c = new MyContainer();
        CountDownLatch latch = new CountDownLatch(1);
        new Thread(()->{
            System.out.println("t2 start ...");
            if(c.size() != 5){
                try {
                    //也可以指定等待时间
                    //latch.await(5000, TimeUnit.MILLISECONDS);
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 over ...");
        }, "t2").start();

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

        new Thread(()->{
            System.out.println("t1 start...");
            for(int i = 0; i < 10; i++){
                c.add(new Object());
                System.out.println("add" + i);
                if(c.size() == 5){
                    //打开门闩,让t2得以执行
                    latch.countDown();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
    }
}
reentrantlock使用(一)
/**
 * 使用reentrantlock可以完成synchronized同样的功能
 * 需要注意的是,必须要手动释放锁(重点),使用syn锁定的话如果遇到异常,
 * jvm会自动释放,但是lock必须手动释放锁,因此经常在finally中进行锁释放。
 */
public class ReentrantLock2 {
    Lock lock = new ReentrantLock();
    void m1(){
        try {
            lock.lock();
            for (int i = 0; i < 10; i++){
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    void m2(){
        lock.lock();
        System.out.println("m2...");
        lock.unlock();
    }

    public static void main(String[] args) {
        ReentrantLock2 r2 = new ReentrantLock2();
        new Thread(r2::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r2::m2).start();
    }
}
reentrantlock使用(二),tryLock使用
/**
 * 使用reentrantlock可以进行"尝试锁定"tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。
 */
public class ReentrantLock3 {
    Lock lock = new ReentrantLock();
    void m1(){
        try {
            lock.lock();
            for(int i = 0; i < 10; i++){
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    /**
     * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行。可以根据tryLock的返回值来判定是否锁定。
     * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中。
     */
    void m2(){

        boolean locked = lock.tryLock();
        System.out.println("m2..." + locked);
        if (locked) lock.unlock();

        /*boolean locked = false;
        try {
            locked = lock.tryLock(5, TimeUnit.SECONDS);
            System.out.println("m2..." + locked);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if(locked) lock.unlock();
        }*/

    }

    public static void main(String[] args) {
        ReentrantLock3 r3 = new ReentrantLock3();
        new Thread(r3::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r3::m2).start();
    }
}
reentrantlock使用(三),lockInterruptibly使用
/**
 * 使用reentranlock可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应。
 * 在一个线程等待锁的过程中,可以被打断。
 */
public class ReentrantLock4 {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Thread t1 = new Thread(()->{
            try {
                lock.lock();
                System.out.println("t1 start");
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                System.out.println("t1 end");
            }catch (InterruptedException e){
                System.out.println("Interrupted...");
            }finally {
                lock.unlock();
            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            try {
                //可以对Interrupt()方法做出响应
                lock.lockInterruptibly();
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end");
            }catch (InterruptedException e){
                System.out.println("Interrupted...");
            }finally {
                lock.unlock();
            }
        });
        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.interrupt();
    }
}
reentrantlock使用(四),公平锁
/**
 * reentrantLock实现公平锁
 */
public class ReentrantLock5 extends Thread{
    //参数为true表示公平锁
    private static ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得锁");
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLock5 r5 = new ReentrantLock5();
        Thread th1 = new Thread(r5);
        Thread th2 = new Thread(r5);
        th1.start();
        th2.start();
    }
}
写一个固定容器同步容器,put、get、getCount方法,能够支持2个生产者线程以及十个消费者线程的阻塞调用
/**
 * 实现功能:写一个固定容器同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及十个消费者线程的阻塞调用。
 */
public class MyContainer1<T> {
    final private LinkedList<T> linkedList = new LinkedList<>();
    final private int MAX = 10; //最多10个元素
    private int count = 0;
    public synchronized void put(T t){
        //这里if和while的区别,如果用if可能会发生这样的问题,
        //容器满了,线程a在等待,然后容器空了一个位置,突然来了一个线程b插了进去,
        //然后容器满了,线程a收到通知会继续执行下去,因为容器满了产生报错。
        //而while不会出现这个问题,因为每次线程消费时,都会判断线程是否已满,然后进行等待。
        //if会继续执行这个,while会进行判断。
        //wait一般都是和while一起使用。
        while (linkedList.size() == MAX){
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        linkedList.add(t);
        ++count;
        this.notifyAll();//通知消费者线程进行消费
    }
    public synchronized T get(){
        T t = null;
        while (linkedList.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        t = linkedList.removeFirst();
        count--;
        this.notifyAll();//通知生产者进行生产
        return t;
    }

    public static void main(String[] args) {
        MyContainer1<String> c = new MyContainer1<>();
        //启动消费者线程
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                for (int j = 0; j < 5; j++)
                    System.out.println(c.get());
            }, "c" + i).start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //启动生产者
        for(int i = 0; i < 2; i++){
            new Thread(()->{
                for(int j = 0; j < 25; j++)
                    c.put(Thread.currentThread().getName() + " " + j);
            }, "p" + i).start();
        }
    }
}
Condition的方式可以更加精确的指定哪些线程被唤醒
/**
 * 实现功能:写一个固定容器同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及十个消费者线程的阻塞调用。
 * 使用Lock和Condition来实现
 * 对比两种方法,Condition的方式可以更加精确的指定哪些线程被唤醒。
 */
public class MyContainer2<T> {
    final private LinkedList<T> linkedList = new LinkedList<>();
    //最多10个元素
    final private int MAX = 10;
    private int count = 0;
    private Lock lock = new ReentrantLock();
    //生产者
    private Condition producer = lock.newCondition();
    //消费者
    private Condition consumer = lock.newCondition();
    public void put(T t){
        try {
            lock.lock();
            while (linkedList.size() == MAX){
                producer.await();
            }
            linkedList.add(t);
            ++count;
            //通知消费者线程进行消费
            consumer.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public T get(){
        T t = null;
        try {
            lock.lock();
            while (linkedList.size() == 0){
                consumer.await();
            }
            t = linkedList.removeFirst();
            count--;
            //通知生产者进行生产
            producer.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return t;
    }

    public static void main(String[] args) {
        MyContainer2<String> c = new MyContainer2<>();
        //启动消费者线程
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                for (int j = 0; j < 5; j++)
                    System.out.println(c.get());
            }, "c" + i).start();
        }

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

        //启动生产者线程
        for(int i = 0; i < 2; i++){
            new Thread(()->{
                for (int j = 0; j < 25; j++)
                    c.put(Thread.currentThread().getName() + " " + j);
            }, "p" + i).start();
        }
    }
}

你可能感兴趣的:(Java)