Atomic类=和=线=程=同=步=新=机=制

Atomic类和线程同步新机制

  • AQS
  • ReentrantLock
    • ReentrantLock vs synchronized
    • ReentrantLock 强大之处
      • lock.tryLock()
      • lock.lockInterruptibly()
      • ReentrantLock还可以指定为公平锁
    • ReentrantLock 获得锁和释放锁
    • ReentrantLock vs synchronized总结
  • CountDownLatch
  • CyclicBarrier
    • CyclicBarrier vs CountDownLatch
  • Phaser
  • *ReadWriteLock(重点)
    • ReadWriteLock vs ReentrantLock
    • 以后还写 synchronized 吗?
  • Semaphore(限流)
  • Exchanger

AQS

这里只是提一嘴,AQS这是高并发最核心的内容。以下所有的东西,都是通过AQS来实现的,也就是说内部是有(线程等待)队列的。
新出现的锁,锁竞争用的都是CAS操作。阻塞都是用的park/unpark。

ReentrantLock

ReentrantLock可重入锁,synchronized 本身就是一种。什么叫可重入,就是我锁了一下之后呢可以对这把锁再锁一下。synchronized必须设计成可重入的,不然子类实现调用父类父类是没法实现的。

/**
 * reentrantlock用于替代synchronized
 * 本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * 这里是复习synchronized最原始的语义
 * @author mashibing
 */
package com.mashibing.juc.c_020;

import java.util.concurrent.TimeUnit;

public class T01_ReentrantLock1 {
	synchronized void m1() {
		for(int i=0; i<10; i++) {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);
			if(i == 2) m2();
		}
		
	}
	
	synchronized void m2() {
		System.out.println("m2 ...");
	}
	
	public static void main(String[] args) {
		T01_ReentrantLock1 rl = new T01_ReentrantLock1();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//new Thread(rl::m2).start();
	}
}

结论:

  • 如果是两个不同的线程一个调m1,一个调m2,此时会存在线程争用的情况
  • 如果是在一个县城里面m1调m2 ,也就是sync方法调sync方法完全是可以的,这就是可重入。
  • 另外如果子类的sync方法调用父类sync方法,如果是sync(this)就是同一把锁。

ReentrantLock vs synchronized

用synchronized的地方完全可以用ReentrantLock来替换。使用reentrantlock可以完成同样的功能。

  • 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)
  • 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
/**
 * reentrantlock用于替代synchronized
 * 由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * 这里是复习synchronized最原始的语义
 * 
 * 使用reentrantlock可以完成同样的功能
 * 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)
 * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
 * @author mashibing
 */
package com.mashibing.juc.c_020;

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

public class T02_ReentrantLock2 {
	Lock lock = new ReentrantLock();

	void m1() {
		try {
			lock.lock(); //synchronized(this)
			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() {
		try {
			lock.lock();
			System.out.println("m2 ...");
		} finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) {
		T02_ReentrantLock2 rl = new T02_ReentrantLock2();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(rl::m2).start();
	}
}

ReentrantLock 强大之处

lock.tryLock()

可以用tryLock尝试锁定,sync如果上来之后搞不定那把锁的话,就会阻塞了。但如果用ReentrantLock,可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待

/**
 * 使用reentrantlock
 * @author mashibing
 */
package com.mashibing.juc.c_020;

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

public class T03_ReentrantLock3 {
	Lock lock = new ReentrantLock();

	void m1() {
		try {
			lock.lock();
			for (int i = 0; i < 3; 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 = 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) {
		T03_ReentrantLock3 rl = new T03_ReentrantLock3();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(rl::m2).start();
	}
}

lock.lockInterruptibly()

sync在wait()以后,必须得等到别人notify()之后才能醒来,不然的话自己是醒不过来的。而以lock.lockInterruptibly()方式加锁,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断

/**
 * 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,
 * 在一个线程等待锁的过程中,可以被打断
 * 
 * @author mashibing
 */
package com.mashibing.juc.c_020;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

public class T04_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 {
				//lock.lock();
				lock.lockInterruptibly(); //可以对interrupt()方法做出响应
				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(); //打断线程2的等待
	}
}

ReentrantLock还可以指定为公平锁

公平锁的意思是,谁等在前面先让谁执行,而不是说让后来的先执行。当我们 new 一个 ReentrantLock,传一个参数为true,表示创建一个公平锁。如果是公平锁,一个线程上来后,先回检查等待队列里头有没有线程,如果有的话就进入等待队列等着,等别人先运行

package com.mashibing.juc.c_020;

import java.util.concurrent.locks.ReentrantLock;

public class T05_ReentrantLock5 extends Thread {
		
	private static ReentrantLock lock=new ReentrantLock(true); //参数为true表示为公平锁,请对比输出结果
    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) {
        T05_ReentrantLock5 rl=new T05_ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}

ReentrantLock 获得锁和释放锁

查看另一篇文章

ReentrantLock vs synchronized总结

  • CAS vs sync锁升级
  • ReentrantLock可以tryLock()并在获取不到锁时执行其他逻辑,sync只能阻塞
  • ReentrantLock可以lockInterrupt()之后通过interrupt()打断,sync只能等待别人notify()
  • ReentrantLock可以用公平锁和非公平锁,sync只能用非公平锁

ReentrantLock是CAS,现在除了sync多数内部用的都是CAS。但是啊,这么聊的话就要聊的很深了。实际上AQS的内部实际上是park()和unpark(),也不是在全部的情况下都是CAS,它还是有一个锁升级的概念,只不过做的比较隐蔽,你在等待队列的时候,如果拿不到,还是会进入阻塞状态,只不过它前面呢会有一个CAS的状态,它不像原来呢,就直接就进入阻塞态了。所以它内有一个阻塞的概念,它用的是什么呢,是LockSupport.park() 和 LockSupport.unpark()。这个后续再深入。

CountDownLatch

count down 倒数,latch 门栓,CountDownLatch 就是“倒数5、4、3、2、1然后门就开了”,这么一个意思。

  • 场景
    它可以用来做什么呢?可以用来监控所有线程是否都执行完,然后再做其他事。也就是完成join相同的功能。
package com.mashibing.juc.c_020;

import java.util.concurrent.CountDownLatch;

public class T06_TestCountDownLatch {
    public static void main(String[] args) {
        usingJoin();
        usingCountDownLatch();
    }

    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) result += j;
                latch.countDown();
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end latch");
    }

    private static void usingJoin() {
        Thread[] threads = new Thread[100];

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) result += j;
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("end join");
    }
}
  • CountDownLatch的几个方法会不会有线程安全问题,需不需要我们自己手动枷锁,不需要,CountDownLatch内部已经做好锁处理了,这个类造出来就是为了解放我们的双手的,如果还要我们自己维护锁,还需要它干嘛。

CyclicBarrier

循环栅栏,这个就像是发令枪一样,什么时候人满了,就把栅栏推倒,然后呢人就出去了。然后栅栏重新起来,然后新来的人满啦,再推倒。就这样循环。

  • 场景(最直观的场景就是面试,哈哈哈)
    • 可以用于多线程计算数据,最后合并计算结果的场景。
    • 一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务
package com.mashibing.juc.c_020;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);

        CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("人满,发车"));

        /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("人满,发车");
            }
        });*/

        for(int i=0; i<100; i++) {
                new Thread(()->{
                    try {
                    	// 相当于计数
                        barrier.await();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
        }
    }
}

CyclicBarrier vs CountDownLatch

  • CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
  • CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

Phaser

  • 场景:遗传算法
  • 模拟了一个结婚的场景:
    结婚分为了几个阶段:
    阶段1)大家到齐(所有人)
    阶段2)大家吃饭(所有人)
    阶段3)大家离开(所有人)
    阶段4)新郎新娘入洞房(新郎新娘)
package com.mashibing.juc.c_020;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

public class T09_TestPhaser2 {
    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();


    static void milliSleep(int milli) {
        try {
            TimeUnit.MILLISECONDS.sleep(milli);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        phaser.bulkRegister(7);

        for(int i=0; i<5; i++) {
            new Thread(new Person("p" + i)).start();
        }

        new Thread(new Person("新郎")).start();
        new Thread(new Person("新娘")).start();
    }

    static class MarriagePhaser extends Phaser {
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {

            switch (phase) {
                case 0:
                    System.out.println("所有人到齐了!" + registeredParties);
                    System.out.println();
                    return false;
                case 1:
                    System.out.println("所有人吃完了!" + registeredParties);
                    System.out.println();
                    return false;
                case 2:
                    System.out.println("所有人离开了!" + registeredParties);
                    System.out.println();
                    return false;
                case 3:
                    System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }


    static class Person implements Runnable {
        String name;

        public Person(String name) {
            this.name = name;
        }

        public void arrive() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 到达现场!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void eat() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 吃完!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void leave() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 离开!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        private void hug() {
            if(name.equals("新郎") || name.equals("新娘")) {
                milliSleep(r.nextInt(1000));
                System.out.printf("%s 洞房!\n", name);
                phaser.arriveAndAwaitAdvance();
            } else {
                phaser.arriveAndDeregister();
                //phaser.register()
            }
        }

        @Override
        public void run() {
            //阶段1)大家到齐
			arrive();
			//阶段2)大家吃饭
            eat();
			//阶段3)大家离开
            leave();
			//阶段4)新郎新娘入洞房
            hug();
        }
    }
}

*ReadWriteLock(重点)

读写锁的概念实际就是共享锁和排他锁。读锁=共享锁,写锁=排他锁。
举个例子:

  1. 一个公司的组织结构,我要想显示组织结构底下有哪些人在网页上访问它,所以这个组织结构经常会被访问到(读),但是只有有人士变动的时候才会更改(改动纪律不大)。
  2. 网站的菜单,也是读的量非常大,但是改的几率很低。

当好多线程来访问它的时候,有的是读线程,有的是写线程,在不产生数据不一致的情况下。这个时候得给它枷锁,不管是读的时候还是写的时候。但是这种情况下效率非常非常的低,尤其是读线程很多的时候,所以做成这样的锁,在我读的时候加这样一把锁,这把锁呢可以给别人也来继续读,但是别人的写线程来申请我就不给他,我正在读呢。这样效率就比较高了。

package com.mashibing.juc.c_020;

import java.util.Random;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class T10_TestReadWriteLock {
    //static Lock lock = new ReentrantLock(); // 互斥锁(排他锁)
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        //Runnable readR = ()-> read(lock);
        Runnable readR = ()-> read(readLock);

        //Runnable writeR = ()->write(lock, new Random().nextInt());
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

		//读锁是共享锁。当一个对象被加上一把读锁的时候,其他的线程也是可以继续读的,所以理论上这18个线程可以1秒钟结束。
        for(int i=0; i<18; i++) new Thread(readR).start();
        //写锁是排他的。当一个线程在写的时候,其他线程既不能读也不能写。
        for(int i=0; i<2; i++) new Thread(writeR).start();
    }
}
  • 读锁是共享锁。当一个对象被加上一把读锁的时候,其他的线程也是可以继续读的。
  • 写锁是排他锁。当一个线程在写的时候,其他线程既不能读也不能写。

ReadWriteLock vs ReentrantLock

所在在“读多写少”的情况下,使用读写锁ReadWriteLock,比单纯的排他锁ReentrantLock效率要大大的提高。

以后还写 synchronized 吗?

以后一般不写这些新的,多数时候还用 synchronized,哈哈哈。只有特别特别追求效率的时候,以后无论什么,上来先用 synchronized 不要想其他的。这个东西呢,面试是第一位的。如果碰到说 synchronized 效率不够了,要求性能上要提高了,好,尝试用其他锁。现在好像用的都是分布式锁(Redis、Zookeeper、数据库(效率较低))。

Semaphore(限流)

叫信号灯。可以是公平锁和非公平锁。在线程内部,通过 s.acquire() 获得这把锁。

  • 场景:
    • 限流。信号的数量等于最多允许多少个线程执行。可以用在卖票,开5个窗口(Semaphore写5),同时在买票的人只能有5个。
package com.mashibing.juc.c_020;

import java.util.concurrent.Semaphore;

public class T11_TestSemaphore {
    public static void main(String[] args) {
        //Semaphore s = new Semaphore(2);
        // 第二个参数控制是否是公平锁
        Semaphore s = new Semaphore(2, true);
        //允许一个线程同时执行
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
            	// 这是一个阻塞操作。可以有好多个线程同时acquire(),但是能acquire()到的只有2个
            	// 获得这把锁。这个线程想要继续往下执行,就要获得这把锁。 
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

Exchanger

交换器,两线程之间交换数据用的。线程之间通信的方式非常多,这只是其中的一种。怎么用呢,看下面:

  • exchanger.exchange(s); 该方法是阻塞的。第一个线程执行到这个方法的时候,把要交换的值扔个exchanger,阻塞,等第二个线程也执行这个方法,然后交换数据。
import java.util.concurrent.Exchanger;

public class T12_TestExchanger {

    static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(()->{
            String s = "T1";
            try {
                s = exchanger.exchange(s); // 该方法是阻塞的。第一个线程执行到这个方法的时候,阻塞,等第二个线程也执行这个方法,然后交换数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

        }, "t1").start();

        new Thread(()->{
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

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

你可能感兴趣的:(多线程高并发)