day12【线程安全解决、并发包】synchronized关键字、售票问题、synchronized同步代码块、synchronized同步方法、Lock锁、并发包

day12【线程安全解决、并发包】

反馈和复习
1.原子类不太懂(CAS机制)
2.建议早上把课程讲完,下午自己有时间可以复习     

在这里插入图片描述

今日内容
1.Synchronized关键字【重点】
2.高并发下JDK提供一堆的线程安全有关类【理解】   

第一章 synchronized关键字【重点】

1.1 AtomicInteger的不足之处
回顾: AtomicInteger能解决什么问题?
    	可以保证对"变量"操作(比如++,--)是原子性(有序的,可见性)
    
无法解决: 多行代码的原子性
    	System.out.println("A");
		System.out.println("B");
1.2 多行代码的原子性安全问题–卖票案例
卖票案例:
/**
 * 卖票任务
 */
public class TicketTask implements Runnable{
    //定义变量,表示初始有100张票
    int count = 100;

    @Override
    public void run() {
        while (true){
            if (count > 0) {
                //先判断,后卖票
                try {Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("卖出第"+count+"张票!");
                //票数要减少
                count--;
            }else {
                break;
            }
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //0.创建卖票任务
        TicketTask tt = new TicketTask();
        //1.创建卖票窗口
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        Thread t3 = new Thread(tt);
        //2.开始卖票
        t1.start();
        t2.start();
        t3.start();
        //线程出现安全问题:
        //a.可能出现重复数据
        //b.可能出现0,甚至-1这种非法数据
    }
}
出现了线程安全问题(原子性问题):
	//a.可能出现重复数据
		原因: 当一个线程执行完卖票后,还没有来得及对票数-1,被其他线程抢走了CPU,导致其他线程也卖出一样的票
    //b.可能出现0,甚至-1这种非法数据
        原因: 当剩下最后一张票时,多线程都经过了if判断 进入卖票的代码块中,然后依次卖出第1,0,-1  
1.3 synchronized关键字介绍
a.synchronized是什么??
    synchronized是Java提供的关键字
b.synchronized的作用??  
    可以让多句代码具有原子性
    (当某个线程执行多句代码的过程中不被其他线程打断)
1.4 解决方案1_同步代码块
格式:
	synchronized(锁对象){
        需要同步的代码(需要保持原子性的代码)
    }

解决代码:
	/**
 * 卖票任务
 */
public class TicketTask implements Runnable{
    //定义变量,表示初始有100张票
    int count = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true){
            //同步代码块
            synchronized (obj){
                if (count > 0) {
                    //先判断,后卖票
                    System.out.println("卖出第"+count+"张票!");
                    //票数要减少
                    count--;
                }
            }

        }
    }
}
1.5 解决方案2_同步方法
格式:
	public synchronized void 方法名(){
     	  需要同步的代码(需要保证原子性的代码)
    }

解决代码:
	/**
 * 卖票任务
 */
public class TicketTask implements Runnable{
    //定义变量,表示初始有100张票
    int count = 100;

    @Override
    public void run() {
        while (true){
           sellTicket();
        }
    }

    public synchronized void sellTicket(){
        if (count > 0) {
            //先判断,后卖票
            System.out.println("卖出第"+count+"张票!");
            //票数要减少
            count--;
        }
    }  
}
注意:
	a.同步代码块和同步方法其实原理是差不多,同步代码块的同步锁我们需要自己指定,而同步方法的同步锁
    ,默认使用当前对象this
    b.同步方法能否是静态方法呢? 可以是static,如果同步方法是static,
	默认使用当前类的字节码文件作为锁对象    
1.6 解决方案3_Lock锁
Lock是一个接口,我们需要使用其实现类 ReentrantLock
ReentrantLock的API:
	public ReentrantLock();  
	public void lock(); //加锁
	public void unlock();//解锁(释放锁)
格式:
	ReentrantLock lock = new ReentrantLock();
	lock.lock(); //加锁
		需要同步的代码块(需要保持原子性的代码)
    lock.unlock();//解锁        

解决代码:
/**
 * 卖票任务
 */
public class TicketTask implements Runnable{
    //定义变量,表示初始有100张票
    int count = 100;
    //创建一个Lock锁
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //加锁
            lock.lock();
                if (count > 0) {
                    //先判断,后卖票
                    System.out.println("卖出第"+count+"张票!");
                    //票数要减少
                    count--;
                }
            lock.unlock();
        }
    }
}

第二章 并发包【理解】

什么是并发包:
	这是JDK提供的,包含一个在高并发情况使用集合或者工具,使用这些集合或者工具类时,能保证高并发情况下是安全的

2.1 CopyOnWriteArrayList
  • ArrayList是线程不安全的

    public class MyThread extends Thread {
    
        public static List<Integer> list = new ArrayList<>();//线程不安全的
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                list.add(i);
            }
            System.out.println("添加完毕!");
        }
    }
    
    public class TestArrayList {
        public static void main(String[] args) throws InterruptedException {
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
            t1.start();
            t2.start();
            Thread.sleep(1000);
            System.out.println("最终集合的长度:" + MyThread.list.size());
        }
    }
    
    
  • CopyOnWriteArrayList是线程安全的

    public class MyThread extends Thread {
    
    //    public static List<Integer> list = new ArrayList<>();//线程不安全的
        public static List<Integer> list = new CopyOnWriteArrayList<>();//保证线程安全
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                list.add(i);
            }
            System.out.println("添加完毕!");
        }
    }
    
    
2.2 CopyOnWriteArraySet
  • HashSet是线程不安全的

    public class MyThread extends Thread {
    
        public static Set<Integer> set = new HashSet<>();//线程不安全的
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                set.add(i);
            }
            System.out.println("添加完毕!");
        }
    }
    
    public class TestSet {
        public static void main(String[] args) throws InterruptedException {
            MyThread t1 = new MyThread();
            t1.start();
            //主线程也添加10000个
            for (int i = 10000; i < 20000; i++) {
                MyThread.set.add(i);
            }
    
            Thread.sleep(1000 * 3);
            System.out.println("最终集合的长度:" + MyThread.set.size());
        }
    }
    
    
  • CopyOnWriteArraySet是线程安全的

    public class MyThread extends Thread {
    
        //public static Set<Integer> set = new HashSet<>();//线程不安全的
    	public static Set<Integer> set = new CopyOnWriteArraySet<>();//线程安全的
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                set.add(i);
            }
            System.out.println("添加完毕!");
        }
    }
    
    
2.3 ConcurrentHashMap
  • HashMap是线程不安全的

    public class MyThread extends Thread {
    
        public static Map<Integer, Integer> map = new HashMap<>();
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                map.put(i, i);//0,0 1,1 2,2 3,3 ... 9999,9999
            }
        }
    }
    
    public class TestMap {
        public static void main(String[] args) throws InterruptedException {
            MyThread t1 = new MyThread();
            t1.start();
    
            for (int i = 10000; i < 20000; i++) {
                MyThread.map.put(i, i);
            }
    
            Thread.sleep(1000 * 2);
            System.out.println("map最终大小:" + MyThread.map.size());
        }
    }
    
    
    
  • Hashtable是线程安全的,但效率低

    public class MyThread1 extends Thread {
        public static Map<Integer, Integer> map = new Hashtable<>(); //线程是安全
    
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                map.put(i, i);
            }
            long end = System.currentTimeMillis();
            System.out.println((end - start) + " 毫秒");
        }
    }
    
    public class TestMap1 {
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 1000; i++) {
                new MyThread1().start();//开启1000个线程
            }
            Thread.sleep(1000 * 20);//由于每个线程执行时间稍长,所以这里多停顿一会
            System.out.println("map的最终大小:" + MyThread1.map.size());
        }
    }
    
    
  • ConcurrentHashMap既安全又效率高

    public class MyThread1 extends Thread {
    //    public static Map<Integer, Integer> map = new Hashtable<>(); //线程是安全,但是效率低
        public static Map<Integer, Integer> map = new ConcurrentHashMap<>(); //线程是安全,但是效率高
    
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                map.put(i, i);
            }
            long end = System.currentTimeMillis();
            System.out.println((end - start) + " 毫秒");
        }
    }
    
    小结:
    	HashMap 线程不安全(多线程不能操作同一个HashMap)
        HashTable 线程安全的(多线程可以操作同一个HashTable),但是效率较低
        ConcurrentHashMap 线程安全的,并且效率比较高    
    
    
  • 为什么Hashtable效率低而ConcurrentHashMap效率高?

    因为HashTable对哈希表进行全表加锁
    而ConcurrentHashMap只对某个桶(链表)局部加锁,并且也同时使用CAS机制  
    
    
2.4 CountDownLatch
  • CountDownLatch的作用

    允许当前线程,等待其他线程完成某种操作之后,当前线程继续执行
    
    
  • CountDownLatch的API

    构造方法:
    	public CountDownLatch(int count); 需要传入计数器,需要等待的线程数
    成员方法:
    	public void await() throws InterruptedException// 让当前线程等待 
        public void countDown() // 计数器进行减1    
    
    
  • CountDownLatch的使用案例

    需求: 
    	线程1要执行打印:A和C,线程2要执行打印:B
        我们需要这样的结果: 线程1 先打印A 线程2打印B 之后 线程1再打印C    
            			A  B  C
    
    public class TestDemo {
        public static void main(String[] args) throws InterruptedException {
            //0.创建一个CountDownLatch
            CountDownLatch latch = new CountDownLatch(1);
            //1.创建两个线程
            Thread t1  = new MyThread1(latch);
    
            Thread t2 = new MyThread2(latch);
    
            t1.start();
    
            Thread.sleep(5000);
            t2.start();
        }
    }
    public class MyThread1 extends Thread {
        private CountDownLatch latch;
        public MyThread1(CountDownLatch latch){
            this.latch = latch;
        }
        @Override
        public void run() {
            System.out.println("A....");
            try {
                latch.await();//让当前线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("C....");
        }
    }
    public class MyThread2 extends Thread {
        private CountDownLatch latch;
    
        public MyThread2(CountDownLatch latch){
            this.latch = latch;
        }
    
        @Override
        public void run() {
            System.out.println("B....");
            //让latch的计数器减少1
            latch.countDown();
        }
    }        
    
    
2.5 CyclicBarrier
  • CyclicBarrier的作用

    让多个线程,都到达了某种要求之后,新的任务才能执行
    
    
  • CyclicBarrier的API

    构造方法:
    public CyclicBarrier(int parties, Runnable barrierAction);
    					需要多少个线程 	所有线程都满足要求了,执行的任务
    
    成员方法:
    public int await();当某个线程达到了,需要调用await()
    
    
  • CyclicBarrier的使用案例

    需求: 部门开会,假设部门有五个人,五个人都到达了才执行开会这个任务
        
    public class TestPersonThread {
        public static void main(String[] args) throws InterruptedException {
            //0.创建一个CyclicBarrier
            CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
                @Override
                public void run() {
                    System.out.println("人都齐了,开会吧");
                }
            });
    
            //1.创建五个线程
            PersonThread p1 = new PersonThread(barrier);
            PersonThread p2 = new PersonThread(barrier);
            PersonThread p3 = new PersonThread(barrier);
            PersonThread p4 = new PersonThread(barrier);
            PersonThread p5 = new PersonThread(barrier);
            //2.开启
            p1.start();
            p2.start();
            p3.start();
            p4.start();
            p5.start();
            //Thread.sleep(6000);
            //System.out.println("人都到了,开会吧...");
            //要求,人没到不开会,都到了立刻开会!!!
        }
    }
    
    public class PersonThread extends Thread {
        private CyclicBarrier barrier;
        public PersonThread(CyclicBarrier barrier){
            this.barrier = barrier;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(new Random().nextInt(6)*1000);
                System.out.println(Thread.currentThread().getName() + " 到了! ");
                //调用 barrier的await 表示线程到了
                try {
                    barrier.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    补充:
    	Math的静态方法
            public static double random(); //获取一个0(包括)到1(不包括)的正小数
    
    
2.6 Semaphore
  • Semaphore的作用

    用于控制并发线程的数量!!
    
    
  • Semaphore的API

    构造方法:
    public Semaphore(int permits); //参数 permits 表示最多允许有多少个线程并发执行
    成员方法:
    public void acquire(); //获取线程的许可证
    public void release(); //释放线程的许可证
    
    
  • Semaphore的使用案例

    public class MyThread extends Thread {
        private Semaphore semaphore;
    
        public MyThread(Semaphore semaphore) {
            this.semaphore = semaphore;
        }
    
        @Override
        public void run(){
            //从Semaphore获取线程的许可
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(Thread.currentThread().getName() + " 进入 时间=" + System.currentTimeMillis());
            try {
                Thread.sleep(100*new Random().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 结束 时间=" + System.currentTimeMillis());
            //归还semaphore线程的许可
            semaphore.release();
        }
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            //0.创建Semaphore
            Semaphore semaphore = new Semaphore(3);
    
            //最多的并发线程数量为1
            for (int i = 0; i < 10; i++) {
                new MyThread(semaphore).start();
            }
        }
    }
    
    
2.7 Exchanger
  • Exchanger的作用

    用于线程间的数据交换
    
    
  • Exchanger的API

    构造方法:
    	public Exchanger<V>();
    成员方法:
    	public V exchange(V x);//参数为发给别的线程的数据,返回值别的线程发过来的数据
    
    
  • Exchanger的使用案例

    public class TestExchanger {
        public static void main(String[] args) throws InterruptedException {
            //0.创建一个线程间数据交互对象
            Exchanger<String> exchanger = new Exchanger<String>();
    
            //1.创建线程A
            ThreadA aThread = new ThreadA(exchanger);
            aThread.start();
    
            //休眠
            Thread.sleep(5000);
    
            ThreadB bThread = new ThreadB(exchanger);
            bThread.start();
        }
    }
    
    public class ThreadA extends Thread {
        private Exchanger<String> exchanger;
    
        public ThreadA(Exchanger<String> exchanger) {
            this.exchanger = exchanger;
        }
    
        @Override
        public void run() {
            System.out.println("线程A,要将礼物AAA,送给线程B...");
            //调用exchanger
            String result = null;
            try {
                result = exchanger.exchange("AAA");//阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程A,获取到线程B的礼物:"+result);
        }
    }
    
    public class ThreadB extends Thread {
        private Exchanger<String> exchanger;
    
        public ThreadB(Exchanger<String> exchanger) {
            this.exchanger = exchanger;
        }
    
        @Override
        public void run() {
            System.out.println("线程B,要将礼物BBB,送给线程A...");
            String result = null;
            try {
                result = exchanger.exchange("BBB");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B,获取到线程A的礼物:"+result);
        }
    }
    
    
    
    总结:
    能够使用同步代码块解决线程安全问题【重点】
        synchronized(锁对象){
        	需要同步的代码(需要保证原子性的代码)
    	}
    	锁对象,可以是任意对象
    能够使用同步方法解决线程安全问题牌【重点】
        public synchronized void 方法名(){
            需要同步的代码(需要保证原子性的代码)
        }
    
    能够使用Lock锁解决线程安全问题牌【重点】
        Lock lock = new ReentrantLock();
    	lock.lock();
     		需要同步的代码(需要保证原子性的代码)
    	lock.unlock();
        
    能够说明volatile关键字和synchronized关键字的区别
        volatile 能解决有序性和可见性
        原子类 能解决变量操作的原子性(有序性和可见性)
        synchronized 能解决多句代码的原子性(有序性和可见性)
    
    能够描述CopyOnWriteArrayList类的作用
        代替多线程的情况,线程安全的ArrayList集合
    能够描述CopyOnWriteArraySet类的作用
        代替多线程的情况,线程安全的HashSet集合
    能够描述ConcurrentHashMap类的作用
        代替多线程的情况,线程安全的HashMap集合(比HashTable效率更好)
    能够描述CountDownLatch类的作用
        可以允许一个线程等待另外一个线程执行完毕后再继续执行
    能够描述CyclicBarrier类的作用
        让一组线程都到达某种条件后再执行某个任务
    能够表述Semaphore类的作用
        控制多线程并发的最大数量 
    能够描述Exchanger类的作用
        用于线程间的通信(数据交换)
    
    
发布了23 篇原创文章 · 获赞 0 · 访问量 1141

猜你喜欢

转载自blog.csdn.net/qq_44845814/article/details/104995229