线程安全&并发包

11 篇文章 0 订阅
11 篇文章 0 订阅

线程安全&并发包

能够使用同步代码块解决线程安全问题(重点)
	synchronized(锁对象){
		访问了共享数据的代码(产生了线程安全问题的代码)
	}
能够使用同步方法解决线程安全问题(重点)
	1.把访问了共享数据的代码,提取出来放到一个方法中
    2.在方法上添加一个同步关键字synchronized
	权限修饰符 synchronized 返回值类型 方法名(参数){
		访问了共享数据的代码(产生了线程安全问题的代码)
	}
能够说明volatile关键字和synchronized关键字的区别
	volatile关键字:只能修饰变量,可以解决变量的可见性,有序性,不能解决原子性
	synchronized关键字:不能修饰变量,可以修饰方法,代码块,使用的范围比volatile广,可以解决:可见性,有序性,原子性
能够描述ConcurrentHashMap类的作用(重点)
	多线程安全的Map集合,效率比Hashtable高
能够描述CountDownLatch类的作用
	作用:计数器 一个线程先执行一部分,然后等待其他线程执行完毕,然后线程在继续执行.
能够描述CyclicBarrier类的作用
	作用:计数器 一个线程等待其他多个线程全部执行完毕,再执行(5个人都到,在开会)
能够描述Semaphore类的作用
	作用:控制并发数量,可以允许几个线程同时进入执行  synchronized只允许一个线程进入执行
能够描述Exchanger类的作用
	作用:两个线程信息交换

第一章 线程安全

之前我们讲过的AtomicInteger可以对“int类型的变量”做原子操作。但如果需要将“很多行代码”一起作为“原子性”执行——一个线程进入后,必须将所有代码行执行完毕,其它线程才能进入,可以使用synchronized关键字——重量级的同步关键字。

AtomicInteger:只能解决一个变量的原子性

synchronized:可以解决一段代码的原子性

1.卖票产生线程安全问题的概述(了解)

请添加图片描述

2.卖票产生线程安全问题的代码实现(重点)

package com.itheima.demo01payTicket;

/*
    卖票案例:
        创建3个线程,卖同100张票(共享的数据)
 */
public class Demo01PayTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建3个线程,卖同100张票
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}
package com.itheima.demo01payTicket;

public class RunnableImpl implements Runnable{
    //定义一个供所有线程共享的票源
    private int ticket = 100;

    //线程任务:卖票
    @Override
    public void run() {
        //增加一个死循环,让线程重复卖票
        while (true){
            //判断ticket大于0,进行卖票
            if(ticket>0){
                //卖票需要10毫秒,让程序睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                ticket--;
            }else{
                break;
            }
        }

    }
}
Thread-0线程正在卖第100张票!
Thread-1线程正在卖第100张票!
Thread-2线程正在卖第100张票!
Thread-2线程正在卖第97张票!
Thread-1线程正在卖第96张票!
Thread-0线程正在卖第95张票!
Thread-2线程正在卖第94张票!
Thread-0线程正在卖第94张票!
Thread-1线程正在卖第94张票!
...
Thread-2线程正在卖第13张票!
Thread-1线程正在卖第13张票!
Thread-1线程正在卖第10张票!
Thread-0线程正在卖第10张票!
Thread-2线程正在卖第10张票!
Thread-0线程正在卖第7张票!
Thread-2线程正在卖第7张票!
Thread-1线程正在卖第7张票!
Thread-1线程正在卖第4张票!
Thread-2线程正在卖第4张票!
Thread-0线程正在卖第4张票!
Thread-0线程正在卖第1张票!
Thread-2线程正在卖第0张票!
Thread-1线程正在卖第-1张票!

请添加图片描述
请添加图片描述

请添加图片描述

继承方式卖票代码

package com.itheima.demo01payTicket;

public class MyThread extends Thread {
    //定义一个供所有线程共享的票源
    private static int ticket = 100;

    //线程任务:卖票
    @Override
    public void run() {
        //增加一个死循环,让线程重复卖票
        while (true){
            //判断ticket大于0,进行卖票
            if(ticket>0){
                //卖票需要10毫秒,让程序睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                ticket--;
            }else{
                break;
            }
        }

    }
}

package com.itheima.demo01payTicket;

public class Demo02PayTicket {
    public static void main(String[] args) {
        //创建3个线程,卖同100张票
        MyThread t0 = new MyThread();
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t0.start();
        t1.start();
        t2.start();
    }
}

3.线程安全问题的产生原理(了解-扩展知识点)

请添加图片描述

4.解决线程安全问题的第一种方式使用同步代码块(重点)

package com.itheima.demo02synchronized;

/*
    卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
    解决线程安全问题的第一种方式:使用同步代码块
    格式:
        synchronized(锁对象){
            访问了共享数据的代码(产生了线程安全问题的代码)
        }
    注意:
        1.同步代码块中的锁对象可以任意的对象  new Person()  new Object   "aaa"
        2.必须保证所有线程使用的都是同一个锁对象
   原理:
        使用一个锁对象,把同步代码块中的代码锁住,只让一个线程获取锁对象,进入到同步中执行,保证安全
 */
public class RunnableImpl implements Runnable{
    //定义一个供所有线程共享的票源
    private int ticket = 100;
    //定义一个锁对象
    //Person p = new Person();
    //Object obj = new Object();
    String str = "abc";//new char[]{'a','b','c'};

    //线程任务:卖票
    @Override
    public void run() {

        //增加一个死循环,让线程重复卖票
        while (true){
           //创建同步代码块
            synchronized (str){
                //判断ticket大于0,进行卖票
                if(ticket>0){
                    //卖票需要10毫秒,让程序睡眠10毫秒
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                    ticket--;
                }else{
                    break;
                }
            }
        }

    }
}
package com.itheima.demo02synchronized;

/*
    卖票案例:
        创建3个线程,卖同100张票(共享的数据)
 */
public class Demo01PayTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建3个线程,卖同100张票
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}

5.同步的原理(了解-扩展知识点)

请添加图片描述

6.解决线程安全问题的第二种方式:使用同步方法(重点)

package com.itheima.demo03synchronized;

/*
    卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
    解决线程安全问题的第二种方式:使用同步方法
    原理:
        把访问了共享数据的代码,提取出来放到一个方法中
        在方法上添加一个同步关键字synchronized
        底层也是使用一个锁对象,把同步方法锁住,只让一个线程进入到方法中执行,保证安全
    格式:
        权限修饰符 synchronized 返回值类型 方法名(参数列表){
            访问了共享数据的代码(产生了线程安全问题的代码)
        }
 */
public class RunnableImpl implements Runnable{
    //定义一个供所有线程共享的票源
    private static int ticket = 100;

    //线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:"+this);
        //增加一个死循环,让线程重复卖票
        while (true){
            //调用同步方法
            //payTicket();
            //调用静态同步方法
            payTicketStatic();
            if(ticket<=0){
                break;
            }
        }

    }

    /*
        定义一个静态的同步方法(了解)
        静态同步方法的锁对象是谁?
            是this吗? 不是 静态方法优先加载到静态区中,还没有this那(对象)
        静态方法使用的锁对象是本类的class文件对象(反射)
             RunnableImpl.class==>唯一
     */
    public static /*synchronized*/ void payTicketStatic(){
        synchronized (RunnableImpl.class){
            //判断ticket大于0,进行卖票
            if(ticket>0){
                //卖票需要10毫秒,让程序睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                ticket--;
            }
        }
    }

    /*
        定义一个同步方法
        同步方法的锁对象是谁?
        是this==>本类对象引用==>RunnableImpl run = new RunnableImpl();
        run:com.itheima.demo03synchronized.RunnableImpl@4554617c
        this:com.itheima.demo03synchronized.RunnableImpl@4554617c
     */
    public synchronized void payTicket(){
        //判断ticket大于0,进行卖票
        if(ticket>0){
            //卖票需要10毫秒,让程序睡眠10毫秒
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
            ticket--;
        }
    }

    /*public  void payTicket(){
        synchronized (this){
            //判断ticket大于0,进行卖票
            if(ticket>0){
                //卖票需要10毫秒,让程序睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                ticket--;
            }
        }
    }*/


}

package com.itheima.demo03synchronized;

/*
    卖票案例:
        创建3个线程,卖同100张票(共享的数据)
 */
public class Demo01PayTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        System.out.println("run:"+run);
        //创建3个线程,卖同100张票
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}

7.解决线程安全问题的第三方式:使用Lock锁(重点)

package com.itheima.demo04Lock;

import java.util.concurrent.locks.ReentrantLock;

/*
    卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
    解决线程安全问题的第三种方式:使用Lock
    java.util.concurrent.locks.Lock:接口
        Lock锁是JDK1.5之后出现的
        Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    接口中的方法:
        void lock() 获取锁。
        void unlock() 释放锁。
    java.util.concurrent.locks.ReentrantLock implements Lock
    使用步骤:
        1.在成员(唯一)位置创建ReentrantLock对象
        2.在可能出现线程安全问题的代码前,调用lock方法获取锁对象
        3.在可能出现线程安全问题的代码后,调用unlock方法释放锁对象
    原理:
        使用lock方法和unlock把一段代码包裹住,只让一个线程获取锁对象,进入执行
 */
public class RunnableImpl implements Runnable{
    //定义一个供所有线程共享的票源
    private int ticket = 100;
    //1.在成员(唯一)位置创建ReentrantLock对象
    private ReentrantLock l = new ReentrantLock();

    //线程任务:卖票
    @Override
    public void run() {
        //增加一个死循环,让线程重复卖票
        while (true){
            //2.在可能出现线程安全问题的代码前,调用lock方法获取锁对象
            l.lock();
                //判断ticket大于0,进行卖票
                if(ticket>0){
                    //卖票需要10毫秒,让程序睡眠10毫秒
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
                    ticket--;
                }/*else{
                    //break;
                    System.exit(0);//终止JVM
                }*/
            //3.在可能出现线程安全问题的代码后,调用unlock方法释放锁对象
            l.unlock();
            if(ticket<=0){
                break;
            }
        }

    }
}
package com.itheima.demo04Lock;

/*
    卖票案例:
        创建3个线程,卖同100张票(共享的数据)
 */
public class Demo01PayTicket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建3个线程,卖同100张票
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }
}

8.CAS与Synchronized

CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?

AtomicInteger:只能解决一个变量的原子性

​ 仅仅是在money.getAndIncrement()方法内部采用了CAS机制

synchronized:可以解决一段代码的原子性

synchronized是从悲观的角度出发:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。

CAS是从乐观的角度出发:

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

CAS这种机制我们也可以将其称之为乐观锁。

第二章 并发包

在JDK的并发包java.util.concurrent里提供了几个非常有用的并发容器(集合)和并发工具类。供我们在多线程开发中进行使用。这些集合和工具类都可以保证高并发的线程安全问题.

1.并发List集合_CopyOnWriteArrayList(重点)

1).java.util.concurrent.CopyOnWriteArrayList(类):它是一个“线程安全”的ArrayList,我们之前学习的java.utils.ArrayList不是线程安全的。
2).如果是多个线程,并发访问同一个ArrayList,我们要使用:CopyOnWriteArrayList

需求:

1.创建一个被多个线程共享使用静态的ArrayList集合对象

2.使用Thread-0线程往集合中添加1000个元素

3.使用main线程往集合中添加1000个元素

4.统计集合的长度

package com.itheima.demo05List;

import java.util.ArrayList;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;

/*
    List集合三大特点:
        1.有序:存储元素和取出的元素顺序是一致的
        2.允许存储重复的元素
        3.包含一些带索引的方法
 */
public class MyThread extends Thread {
    //1.创建一个被多个线程共享使用静态的ArrayList集合对象
    /*
        java.uti.ArrayList:是一个多线程不安全的集合
     */
    //public static ArrayList<Integer> list = new ArrayList<>();

    /*
        java.util.Vector<E>集合
            是JDK1.0时期的单列集合
            与新 collection 实现不同,Vector 是同步的。
        Vector集合采用同步技术,保证多线程使用集合的安全
        同步技术使用synchronized,是一个悲观锁,效率低下
     */
    //public static Vector<Integer> list = new Vector<>();

    /*
        java.util.concurrent.CopyOnWriteArrayList<E>集合 implements List<E>接口
            是JDK1.5之后出现的
        CopyOnWriteArrayList采用乐观锁,保证多线程使用集合的安全
        乐观锁使用CAS机制,是一个乐观锁,效率比synchronized高
        CopyOnWriteArrayList集合中的方法和List接口一模一样
     */
    public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();


    @Override
    public void run() {
        //2.使用Thread-0线程往集合中添加1000个元素
        System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
        for (int i = 0; i < 1000; i++) {
            list.add(i);//{0,1,2,3...999}
            try {
                Thread.sleep(1);//提高出现线程安全问题的概率
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
    }
}
package com.itheima.demo05List;

public class Demo01List {
    public static void main(String[] args) throws InterruptedException {
        //创建MyThread对象,调用start方法,开启一个新的线程执行run方法
        MyThread mt = new MyThread();
        mt.start();

        //3.使用main线程往集合中添加1000个元素
        System.out.println("main线程往集合中添加1000个元素");
        for (int i = 0; i < 1000; i++) {
            MyThread.list.add(i);//{0,1,2,3...999}
            Thread.sleep(1);//提高出现线程安全问题的概率
        }
        System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
        Thread.sleep(3000);
        System.out.println("两个线程都添加元素结束,统计集合的长度:"+MyThread.list.size());
    }
}

ArrayList集合并发的问题:

1.存储的元素个数不对

2.会引发索引越界异常(底层是一个数组,数组每次添加元素,创建新的数组)

main线程往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:2000
    
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1985

main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:1996    

2.并发Set集合_CopyOnWriteArraySet(重点)

需求:

1.创建一个被多个线程共享使用静态的HashSet集合对象

2.使用Thread-0线程往集合中添加1000个元素

3.使用main线程往集合中添加1000个元素

4.统计集合的长度

package com.itheima.demo06Set;

import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArraySet;

/*
    Set集合特点:
       1.不允许存储重复的元素
       2.不包含带索引的方法
 */
public class MyThread extends Thread {
    //1.创建一个被多个线程共享使用静态的HashSet集合对象
    /*
        java.uti.HashSet:是一个多线程不安全的集合
     */
    //public static HashSet<Integer> set = new HashSet<>();


    /*
        java.util.concurrent.CopyOnWriteArraySet<E>集合
            是JDK1.5之后出现的
        CopyOnWriteArraySet采用乐观锁,保证多线程使用集合的安全
        乐观锁使用CAS机制,是一个乐观锁
        CopyOnWriteArraySet里边的方法和HashSet是一样的
     */
    public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();


    @Override
    public void run() {
        //2.使用Thread-0线程往集合中添加1000个元素
        System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
        for (int i = 0; i < 1000; i++) {
            set.add(i);//{0,1,2,3...999}
            try {
                Thread.sleep(1);//提高出现线程安全问题的概率
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
    }
}
package com.itheima.demo06Set;

public class Demo01Set {
    public static void main(String[] args) throws InterruptedException {
        //创建MyThread对象,调用start方法,开启一个新的线程执行run方法
        MyThread mt = new MyThread();
        mt.start();

        //3.使用main线程往集合中添加1000个元素
        System.out.println("main线程往集合中添加1000个元素");
        for (int i = 1000; i < 2000; i++) {
            MyThread.set.add(i);//{1000,1001,1002,1003...1999}
            Thread.sleep(1);//提高出现线程安全问题的概率
        }
        System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
        Thread.sleep(3000);
        System.out.println("两个线程都添加元素结束,统计集合的长度:"+ MyThread.set.size());
    }
}

HashSet集合存在并发问题:

main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1992

3.并发Map集合_ConcurrentHashMap(重点)

需求:

1.创建一个被多个线程共享使用静态的HashMap集合对象

2.使用Thread-0线程往集合中添加1000个元素

3.使用main线程往集合中添加1000个元素

4.统计集合的长度

package com.itheima.demo07Map;


import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

/*
    Map集合特点:
       1.是一个双列集合
       2.key不允许重复的,value可以重复
       3.key和value是一一对应
   ---------------------------------------
   面向复制编程:CV大法
   ---------------------------------------
   ctrl+r:查找并替换
 */
public class MyThread extends Thread {
    //1.创建一个被多个线程共享使用静态的HashMap集合对象
    /*
        java.uti.HashMap:是一个多线程不安全的集合
     */
    //public static HashMap<Integer,Integer> map = new HashMap<>();

    /*
        java.util.Hashtable<K,V>集合
            是JDK1.0时期的双列集合
            不像新的 collection 实现,Hashtable 是同步的
        Hashtable集合采用同步技术,保证多线程使用集合的安全
        同步技术使用synchronized,是一个悲观锁,效率低下
     */
    //public static Hashtable<Integer,Integer> map = new Hashtable<>();

    /*
        java.util.concurrent.ConcurrentHashMap<K,V><集合
            是JDK1.5之后出现的
        ConcurrentHashMap采用乐观锁,保证多线程使用集合的安全
        使用CAS机制,是一个乐观锁,效率比synchronized高
        ConcurrentHashMap集合中的方法和HashMap集合一模一样
     */
    public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();


    @Override
    public void run() {
        //2.使用Thread-0线程往集合中添加1000个元素
        System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
        for (int i = 0; i < 1000; i++) {
            map.put(i,i);//{0-0,1-1,2-2,3-3...1999-1999}
            try {
                Thread.sleep(1);//提高出现线程安全问题的概率
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
    }
}
package com.itheima.demo07Map;

public class Demo01Map {
    public static void main(String[] args) throws InterruptedException {
        //创建MyThread对象,调用start方法,开启一个新的线程执行run方法
        MyThread mt = new MyThread();
        mt.start();

        //3.使用main线程往集合中添加1000个元素
        System.out.println("main线程往集合中添加1000个元素");
        for (int i = 1000; i < 2000; i++) {
            MyThread.map.put(i,i);//{1000-1000,1001-1001,1002-1002,...1999-1999}
            Thread.sleep(1);//提高出现线程安全问题的概率
        }
        System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
        Thread.sleep(3000);
        System.out.println("两个线程都添加元素结束,统计集合的长度:"+ MyThread.map.size());
        System.out.println(MyThread.map);
    }
}

hashMap集合存在并发问题:

main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1990

main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:1995    

比较ConcurrentHashMap和Hashtable的效率

Java类库中,从1.0版本也提供一个线程安全的Map:Hashtable
Hashtable和ConcurrentHashMap有什么区别:
Hashtable采用的synchronized——悲观锁,效率更低。
ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。

需求:

1.创建一个被多个线程共享使用静态的Hashtable集合(ConcurrentHashMap集合)对象

2.开启1000个线程,每个线程往集合中存储100000个数据

package com.itheima.demo08Map;

import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class MyThread extends Thread{
    //1.创建一个被多个线程共享使用静态的Hashtable集合(ConcurrentHashMap集合)对象
    //public static Hashtable<Integer,Integer> map = new Hashtable<>();
    public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();
    @Override
    public void run() {
        //每个线程往集合中存储100000个数据
        long s = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            map.put(i,i);
        }
        long e = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"线程存储10W个数据耗时:"+(e-s)+"毫秒!");
    }
}
package com.itheima.demo08Map;

public class Demo01 {
    public static void main(String[] args) {
        //开启1000个线程
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }
    }
}

Hashtable效率低下原因:

public synchronized V put(K key, V value) 
public synchronized V get(Object key)

Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法,其他线程也访问Hashtable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

4.多线程协作_CountDownLatch(会用)

CountDownLatch允许一个或多个线程等待其他线程完成操作。

例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。

CountDownLatch构造方法:

public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象

CountDownLatch重要方法:

public void await() throws InterruptedException// 让当前线程等待,直到CountDownLatch计数器的值为0的时候,让线程继续执行
public void countDown()	// 计数器进行减1

  • 无协作案例

    package com.itheima.demo09CountDownLatch;
    
    public class MyThread1 extends Thread{
        @Override
        public void run() {
            System.out.println("A");
            System.out.println("C");
        }
    }
    
    package com.itheima.demo09CountDownLatch;
    
    public class MyThread2 extends Thread{
        @Override
        public void run() {
            System.out.println("B");
        }
    }
    
    package com.itheima.demo09CountDownLatch;
    
    /*
        例如:线程1要执行打印:A和C,线程2要执行打印:B,
              但线程1在打印A后,要线程2打印B之后才能打印C,
              所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。
     */
    public class Demo01 {
        public static void main(String[] args) {
            new MyThread1().start();
            new MyThread2().start();
        }
    }
    
  • 示例

1). 制作线程1:

package com.itheima.demo10CountDownLatch;

import java.util.concurrent.CountDownLatch;

public class MyThread1 extends Thread{
    private CountDownLatch cdl;

    public MyThread1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("A");
        try {
            //让当前线程等待,直到CountDownLatch计数器的值为0的时候,让线程继续执行
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}

2). 制作线程2:

package com.itheima.demo10CountDownLatch;

import java.util.concurrent.CountDownLatch;

public class MyThread2 extends Thread{
    private CountDownLatch cdl;

    public MyThread2(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("B");
        cdl.countDown();//计数器进行减1
    }
}

3).制作测试类:

package com.itheima.demo10CountDownLatch;

import java.util.concurrent.CountDownLatch;

/*
    例如:线程1要执行打印:A和C,线程2要执行打印:B,
          但线程1在打印A后,要线程2打印B之后才能打印C,
          所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。
    java.util.concurrent.CountDownLatch
        一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
    注意:
        必须保证多个线程使用的是同一个CountDownLatch对象
 */
public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        //创建CountDownLatch对象,给两个线程共享使用
        CountDownLatch cdl = new CountDownLatch(1);//内部计数器的值为1
        new MyThread1(cdl).start();
        Thread.sleep(1000);//睡眠1秒钟,保证线程1先执行
        new MyThread2(cdl).start();
    }
}

4). 执行结果:
会保证按:A B C的顺序打印。

说明:

CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。

CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。

await()方法的线程阻塞状态解除,继续执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHNED831-1645620838867)(img/1638429791745.png)]

5.多线程协作_CyclicBarrier(会用)

概述

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

例如:公司召集5名员工开会,等5名员工都到了,会议开始。

我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。

CyclicBarrier构造方法:

public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景

CyclicBarrier重要方法:

public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞

  • 无协作案例

    package com.itheima.demo11CyclicBarrier;
    
    /*
        创建的员工线程
     */
    public class PersonThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"来到了会议室!");
        }
    }
    
    package com.itheima.demo11CyclicBarrier;
    
    /*
        会议线程
     */
    public class MeetingThread extends Thread{
        @Override
        public void run() {
            System.out.println("人到齐了,我们开始开会!");
        }
    }
    
    package com.itheima.demo11CyclicBarrier;
    
    /*
        例如:公司召集5名员工开会,等5名员工都到了,会议开始。
        我们创建5个员工线程,1个开会线程,几乎同时启动,
        使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
     */
    public class Demo01CyclicBarrier {
        public static void main(String[] args) {
            PersonThread p1 = new PersonThread();
            PersonThread p2 = new PersonThread();
            PersonThread p3 = new PersonThread();
            PersonThread p4 = new PersonThread();
            PersonThread p5 = new PersonThread();
            p1.start();
            p2.start();
            p3.start();
            p4.start();
            p5.start();
    
            MeetingThread mt = new MeetingThread();
            mt.start();
        }
    }
    
  • 示例代码:

1). 制作员工线程:

package com.itheima.demo12CyclicBarrier;

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

/*
    创建的员工线程
 */
public class PersonThread extends Thread{
    private CyclicBarrier cb;

    public PersonThread(CyclicBarrier cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"来到了会议室!");
        try {
            cb.await();//把CyclicBarrier内部计数器的值-1
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

2). 制作开会线程:

package com.itheima.demo11CyclicBarrier;

/*
    会议线程
 */
public class MeetingThread extends Thread{
    @Override
    public void run() {
        System.out.println("人到齐了,我们开始开会!");
    }
}

3). 制作测试类:

package com.itheima.demo12CyclicBarrier;

import java.util.concurrent.CyclicBarrier;

/*
    例如:公司召集5名员工开会,等5名员工都到了,会议开始。
    我们创建5个员工线程,1个开会线程,几乎同时启动,
    使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
    java.util.concurrent.CyclicBarrier
        一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点
    构造方法:
        CyclicBarrier(int parties, Runnable barrierAction)
        参数:
           int parties:设置屏障的数量,想让几个线程执行完,在执行其他线程;数量就设置几
                想让5个人都到齐之后,在执行会议线程
                屏障的数量设置:5
           Runnable barrierAction:达到屏障之后,开始执行的线程(会议线程)
     成员方法:
           int await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
 */
public class Demo01CyclicBarrier {
    public static void main(String[] args) {
        //创建CyclicBarrier对象,计数器的值,设置为5;当计数器的值变成0,就会执行参数传递的MeetingThread线程
        MeetingThread mt = new MeetingThread();
        CyclicBarrier cb = new CyclicBarrier(5,mt);

        PersonThread p1 = new PersonThread(cb);
        PersonThread p2 = new PersonThread(cb);
        PersonThread p3 = new PersonThread(cb);
        PersonThread p4 = new PersonThread(cb);
        PersonThread p5 = new PersonThread(cb);
        p1.start();
        p2.start();
        p3.start();
        p4.start();
        p5.start();

    }
}

4). 执行结果:

Thread-1来到了会议室!
Thread-2来到了会议室!
Thread-3来到了会议室!
Thread-4来到了会议室!
Thread-5来到了会议室!
人到齐了,我们开始开会!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cw5feOis-1645620838867)(img/1638431819245.png)]

使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。

需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

6.并发数量控制_Semaphore(会用)

Semaphore的主要作用是控制线程的并发数量。

synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。

Semaphore可以设置同时允许几个线程执行。

Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。

Semaphore构造方法:

public Semaphore(int permits)						permits 表示许可线程的数量
public Semaphore(int permits, boolean fair)			fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程

Semaphore重要方法:

public void acquire() 表示获取许可  lock
public void release() 表示释放许可  unlock
  • 示例一:同时允许1个线程执行

1):使用同步代码块

package com.itheima.demo13Semaphore;

/*
    需求:
        创建5名学生线程
        让5名学生线程进入到一个教室中去参观
        要求每次只能一名学生进入到教室参观
 */
public class Demo01Semaphore {
    public static void main(String[] args) {
        //创建一个教室
        ClassRoom classRoom = new ClassRoom();
        //创建5名学生线程
        for (int i = 0; i < 5; i++) {
            new StudentThread(classRoom).start();
        }
    }
}
package com.itheima.demo13Semaphore;

/*
    定义教室类
 */
public class ClassRoom {
    //定义一个线程进入到教室参观的方法
    public void intoClassRoom(){
        //要求每次只能一名学生进入到教室参观,使用同步代码块
        synchronized (this){
            System.out.println(Thread.currentThread().getName()+"线程...进入到教室参观!");
            try {
                Thread.sleep(2000);//在教室参观2秒钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程...参观2秒钟之后离开教室!");

        }
    }
}
package com.itheima.demo13Semaphore;

/*
    学生线程
 */
public class StudentThread extends Thread {
    //定义一个教室的变量:保证5个学生进入的是同一个教室
    private ClassRoom classRoom;

    public StudentThread(ClassRoom classRoom) {
        this.classRoom = classRoom;
    }

    @Override
    public void run() {
        //让学生进入到教室去参观
        classRoom.intoClassRoom();
    }
}

4). 结果:

Thread-0线程...进入到教室参观!
Thread-0线程...参观2秒钟之后离开教室!
Thread-4线程...进入到教室参观!
Thread-4线程...参观2秒钟之后离开教室!
Thread-3线程...进入到教室参观!
Thread-3线程...参观2秒钟之后离开教室!
Thread-2线程...进入到教室参观!
Thread-2线程...参观2秒钟之后离开教室!
Thread-1线程...进入到教室参观!
Thread-1线程...参观2秒钟之后离开教室!
  • 示例二:同时允许2个线程同时执行
package com.itheima.demo14Semaphore;

import java.util.concurrent.Semaphore;

/*
    定义教室类
    java.util.concurrent.Semaphore:可以控制线程的并发数量
        控制同时有几个线程执行
    构造方法:
        Semaphore(int permits) 参数传递允许执行的线程数量
    成员方法:
        void acquire() 表示获取许可,相当于lock(获取锁)
        void release() 释放一个许可,相当于unlock(释放锁)
 */
public class ClassRoom {
    //创建Semaphore对象,参数传递2,表示允许同时有2个线程进入执行
    Semaphore semaphore = new Semaphore(2);

    //定义一个线程进入到教室参观的方法
    public void intoClassRoom(){
        try {
            //要求每次可以进入两名学生进入到教室参观
            semaphore.acquire();//表示获取许可
            System.out.println(Thread.currentThread().getName()+"线程...进入到教室参观!");
            try {
                Thread.sleep(2000);//在教室参观2秒钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程...参观2秒钟之后离开教室!");
            semaphore.release();//释放一个许可
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

2). 再次执行结果:

Thread-0线程...进入到教室参观!
Thread-1线程...进入到教室参观!
Thread-0线程...参观2秒钟之后离开教室!
Thread-1线程...参观2秒钟之后离开教室!
Thread-2线程...进入到教室参观!
Thread-3线程...进入到教室参观!
Thread-3线程...参观2秒钟之后离开教室!
Thread-2线程...参观2秒钟之后离开教室!
Thread-4线程...进入到教室参观!
Thread-4线程...参观2秒钟之后离开教室!

7.线程信息交互_Exchanger(会用)

概述

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

Exchanger构造方法:

public Exchanger()

Exchanger重要方法:

public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
  • 示例一:exchange方法的阻塞特性

1).制作线程A,并能够接收一个Exchanger对象:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

/*
    java.util.concurrent.Exchanger<V>:用于两个线程数据交换
    构造方法:
        Exchanger() 创建一个新的 Exchanger。
    成员方法:
        public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
    注意:
        保证交互数据的两个线程使用的是同一个Exchanger对象
 */
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开始执行");
        System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
        try {
            String result = exchanger.exchange("100元");
            System.out.println("线程A得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2). 制作main()方法:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

public class Demo01 {
    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        new ThreadA(exchanger).start();
    }
}

3).执行结果:只有线程A执行,线程B没有执行,没有人和线程A交互数据,exchange会一直等待

线程A开始执行
线程A给线程B100元钱,并从线程B获取一张火车票!
  • 示例二:exchange方法执行交换

1).制作线程A:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

/*
    java.util.concurrent.Exchanger<V>:用于两个线程数据交换
    构造方法:
        Exchanger() 创建一个新的 Exchanger。
    成员方法:
        public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
    注意:
        保证交互数据的两个线程使用的是同一个Exchanger对象
 */
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开始执行");
        System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
        try {
            String result = exchanger.exchange("100元");
            System.out.println("线程A得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2).制作线程B:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

/*
    java.util.concurrent.Exchanger<V>:用于两个线程数据交换
    构造方法:
        Exchanger() 创建一个新的 Exchanger。
    成员方法:
        public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
    注意:
        保证交互数据的两个线程使用的是同一个Exchanger对象
 */
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开始执行");
        System.out.println("线程B给线程A一张火车票,并从线程A得到100元钱!");
        try {
            String result = exchanger.exchange("火车票");
            System.out.println("线程B得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3).制作测试类:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

public class Demo01 {
    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        new ThreadA(exchanger).start();
        new ThreadB(exchanger).start();
    }
}

4).执行结果:

线程A开始执行
线程B开始执行
线程B给线程A一张火车票,并从线程A得到100元钱!
线程A给线程B100元钱,并从线程B获取一张火车票!
线程A得到的东西:火车票
线程B得到的东西:100

请添加图片描述

  • 示例三:exchange方法的超时

1).制作线程A:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/*
    java.util.concurrent.Exchanger<V>:用于两个线程数据交换
    构造方法:
        Exchanger() 创建一个新的 Exchanger。
    成员方法:
        V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
        V exchange(V x, long timeout, TimeUnit unit)
            V x:给对方的东西
            long timeout:设置等待的时长
            TimeUnit unit:设置等待的时间单位
                常量:静态常量,通过类名可以直接使用
                    TimeUnit.DAYS:天
                    TimeUnit.HOURS:时
                    TimeUnit.MINUTES:分钟
                    TimeUnit.SECONDS:秒
    注意:
        保证交互数据的两个线程使用的是同一个Exchanger对象
 */
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开始执行");
        System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
        try {
            //String result = exchanger.exchange("100元");
            //设置等待5秒钟没有人来交换数据,抛出TimeoutException异常,结束等待
            String result = exchanger.exchange("100元",5, TimeUnit.SECONDS);
            System.out.println("线程A得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

2).制作测试类:

package com.itheima.demo15Exchanger;

import java.util.concurrent.Exchanger;

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        //创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        new ThreadA(exchanger).start();
        Thread.sleep(10000);
        new ThreadB(exchanger).start();
    }
}

3).测试结果:

线程A开始执行
线程A给线程B100元钱,并从线程B获取一张火车票!
java.util.concurrent.TimeoutException
	at java.util.concurrent.Exchanger.exchange(Exchanger.java:626)
	at com.itheima.demo15Exchanger.ThreadA.run(ThreadA.java:39)
线程B开始执行
线程B给线程A一张火车票,并从线程A得到100元钱!

使用场景

使用场景:可以做数据校对工作

需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,

并对两个文件数据进行校对,看看是否录入一致,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值