发布于2021-03-13 14:48 阅读(737) 评论(0) 点赞(6) 收藏(1)
最近刚学完多线程,趁着自己还没忘记,现总结总结,大家凑合着看看吧
1.1并行与并发
并行:多个对象同一时刻执行多个任务
并发:单个对象在同一时间段交替执行多个任务
1.2进程和线程
进程:指正在运行的一个程序(cpu分配资源的基本单位)
线程:正在运行的程序中的一个执行任务(执行单元)(进程中分配资源的基本单位)
1.3多线程的三种实现方法
1.3.1继承Thread类
一个类通过继承Thread类,重写父类的run()方法,来实现多线程,通过这种方法来实现多线程,直接创建对象调用start()方法来开始线程;
start()方法实际上是开辟新的栈内存,在新的栈内存中执行线程任务
所以要想开始新的线程,必须调用start()方法
1.3.2实现Runnable接口
一个类可以通过实现Runnable接口来实现多线程,重写接口中的run方法,然后将实现了接口的类的对象作为参数传递给Thread对象,再通过调用start()方法来开启线程:new Thread(Runnable对象)
1.3.3实现Callable接口
一个类可以通过实现Callable接口来实现多线程,重写接口的call方法,然后创建类的对象作为参数传递给FutureTask对象,然后将FutureTask对象作为参数传递给Thread对象:
- FutureTask ft = new FutrueTask(Call对象);
- Thread t = new Thread(ft);
- t.start();
call方法是有一个返回值的,Callable是一个泛型接口,泛型的类型必须和call方法的返回值类型相同
在线程运行结束后,可以通过FutureTask对象调用get方法来获取返回值
1.3.4三种方式的比较
继承Thread相比较起来编写代码少,但是该类无法去继承其他类,降低了可扩展性
其他两种实现接口的方式提高了可扩展性,在实现多线程时还可以去继承其他类(以后用实现接口较多)
3.1设置获取名字
- String getName();//获取线程的名字
- Thread(String name);//通过构造方法给线程定义一个名字
- setName(String name);//设置线程的名字
3.2获取当前线程对象
public static Thread currentThread();//获取当前调用方法的线程对象
3.3睡眠方法
public static void sleep(long time);//设置当前线程休眠time时间
3.4线程的优先级
声明:线程的优先级的高低只是说明其抢占CPU时间片的概率的大小,并不能保证在CPU空闲时一定能抢到执行权
-
- int getPriority();//获取当前线程的优先级
- void setPriority(int priority)//给线程设置优先级
- 注:优先级默认为5,范围为MIN_PRIORITY(1)-MAX_PRIORITY(10)
3.5守护线程
void setDaemon(boolean false);//设置一个线程为守护线程
守护线程时为了给其他线程做辅助工作,当普通线程结束时不管守护线程是否结束,它都会随之结束
3.1产生安全问题原因
当多个线程共享资源时,并且多个线程都对资源数据进行增删改操作时,会出现安全问题。
3.2解决方法
3.2.1同步代码块
同步代码块的格式:
synchronized(锁对象){//多线程对共享资源操作的代码 }
注意:①锁对象可以是任意对象
②多线程必须用同一把锁
同步代码块解决多线程安全问题的原理:使一个线程在对共享数据进行操作时,其他线程无法对其进行操作
3.2.2同步方法
同步方法的格式:
public [static] synchronized 返回值类型 方法名(){}
static同步方法 锁对象是 this
非static同步方法 锁对象是 类名.class
3.2.3创建锁对象
可以自己实例化一个锁对象:Lock lock = new ReenTrantLock
在需要加锁的代码位置lock();需要解开锁的位置unlock();
相关代码:
这里是同步代码块写了一个经典的卖票的线程安全问题,同步方法不做演示
-
- public class MySynchronizedTest1 {
- public static void main(String[] args) {
- Ticket ticket = new Ticket();
- Thread t1 = new Thread(ticket);
- Thread t2 = new Thread(ticket);
- Thread t3 = new Thread(ticket);
-
- t1.setName("窗口1");
- t2.setName("窗口2");
- t3.setName("窗口3");
-
- t1.start();
- t2.start();
- t3.start();
- }
- }
- class Ticket implements Runnable{
- //定义票的数量
- private int ticket = 100;
- private static Object obj = new Object();
- @Override
- public void run() {
- while(true){
- //设置同步代码块,将操作共享数据的代码放到synchronized(Object obj){}的大括号中
- synchronized (obj){
- //就是当前的线程对象.
- if(ticket <= 0){
- break;//说明票已经卖完了
- }else{
- //卖每一张票需要时间,所以这里让它睡眠一下再继续卖下一张
- try {
- Thread.sleep(150);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- ticket--;
- System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
- }
- }
- }
- }
- }
3.3死锁问题
死锁:由于锁的嵌套导致线程A等待线程B的锁,线程B等待线程A的锁,所以导致程序无法正常执行
代码演示:
- //死锁
- public class MySynchronizedTest3 {
- public static void main(String[] args) {
- Object objA = new Object();
- Object objB = new Object();
-
- new Thread(()->{
- while(true){
- synchronized (objA){
- synchronized (objB){
- System.out.println("我在走路");
- }
- }
- }
- }).start();
- new Thread(()->{
- while(true){
- synchronized (objB){
- synchronized (objA){
- System.out.println("你在走路");
- }
- }
- }
- }).start();
- }
- }
3.4生产者-消费者
这里举例说明可以更好的理解:
①顾客来到餐厅发现没有吃的,就会等待wait()
②顾客来到餐厅发现有吃的,那么就会吃掉吃的,然后通知厨师去做notify()
③厨师发现现在有吃的,那就等待wait()
④厨师发现没有吃的,那么就会做食物,然后通知顾客notify()
生产者-消费者主要解决了让多个线程按照一定的次序来协作完成某个功能
代码实现主要使用三个方法:wait() notify() notifyAll();
注意:这三个方法的调用者都是锁对象
附上代码如下:
- //桌子类,存放共享资源
- public class Desk {
- //定义一个flag表示桌子上是否有食物
- private boolean flag;
- //定义食物数量
- private int FoodCount;
- //定义共用的锁
- private final Object Lock = new Object();
-
- public Desk() {
- this(false,10);
- }
-
- public Desk(boolean flag, int foodCount) {
- this.flag = flag;
- FoodCount = foodCount;
- }
-
- public boolean isFlag() {
- return flag;
- }
-
- public void setFlag(boolean flag) {
- this.flag = flag;
- }
-
- public int getFoodCount() {
- return FoodCount;
- }
-
- public void setFoodCount(int foodCount) {
- FoodCount = foodCount;
- }
-
- public Object getLock() {
- return Lock;
- }
-
- @Override
- public String toString() {
- return "Desk{" +
- "flag=" + flag +
- ", FoodCount=" + FoodCount +
- ", Lock=" + Lock +
- '}';
- }
- }
- //消费者类(顾客)
- public class Foodie extends Thread{
- private Desk desk;
- public Foodie(Desk desk) {
- this.desk = desk;
- }
-
- @Override
- public void run() {
- while(true){
- synchronized (desk.getLock()){
- if(desk.getFoodCount() == 0){
- break;
- }else{
- if(desk.isFlag()){//代表桌子上有食物
- System.out.println("吃货正在吃倒数第"+desk.getFoodCount()+"个食物");
- desk.setFlag( false);//吃完之后将flag变为flase表示桌子上没有食物了
-
- //Desk.FoodCount--;//食物数量减一
- desk.setFoodCount(desk.getFoodCount()-1);
-
- desk.getLock().notifyAll();//唤醒等待的线程
- }else{
- try {
- desk.getLock().wait();//如果为false,说明没有食物,那么进入等待状态,释放掉锁
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- }
- //生产者类(厨师)
- public class Cooker extends Thread{
- private Desk desk;
- public Cooker(Desk desk) {
- this.desk = desk;
- }
-
- @Override
- public void run() {
- while(true){
- synchronized (desk.getLock()){
- if(desk.getFoodCount() == 0){
- break;
- }else{
- if(!desk.isFlag()){
- System.out.println("厨师正在做倒数第"+desk.getFoodCount()+"个食物,还剩"+(desk.getFoodCount()-1)+"个食物");
- desk.setFlag(true);
- desk.getLock().notifyAll();
- }else{
- try {
- desk.getLock().wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- }
- //测试类
- public class Test {
- public static void main(String[] args) {
- Desk desk = new Desk();
- Foodie f = new Foodie(desk);
- Cooker c = new Cooker(desk);
- f.start();
- c.start();
- }
- }
3.5阻塞队列
3.5.1阻塞队列的使用
阻塞出现的原因:程序去完成一个功能时,由于某种原因,现在无法完成,程序会停住(后面的代码就不能再执行了),直到该功能完成为止。
阻塞队列结合生产者-消费者使用可以大大提高代码效率,并且不需要加锁,因为阻塞队列的底层就是 锁+wait+notify
3.5.2阻塞队列的创建和方法
BlockingQueue:
ArrayBlockingQueue(int capacity)
LinkedBlockingQueue();
方法:
往阻塞队列中放元素:put
从阻塞队列中取元素:take
3.5.3分析
在消费者-生产者机制中使用阻塞队列,相当于给生产者提供了一个可以存放多个产品的队列,它生产了产品就放到阻塞队列中,不必等待消费者消费了产品之后再唤醒它再去生产下一个产品;而消费者也同理,它可以直接从阻塞队列中取产品,不用等生产者一个一个生产。
附上代码如下:
- import java.util.concurrent.ArrayBlockingQueue;
- //生产者类
- public class Cooker extends Thread{
- private ArrayBlockingQueue<String> arrayBlockingQueue;
- public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue) {
- this.arrayBlockingQueue = arrayBlockingQueue;
- }
-
- @Override
- public void run() {
- while (true){
- try {
- arrayBlockingQueue.put("汉堡包");
- System.out.println("往阻塞队列中放一个汉堡包");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- //消费者类
- import java.util.concurrent.ArrayBlockingQueue;
-
- public class Foodie extends Thread{
- private ArrayBlockingQueue<String> arrayBlockingQueue;
- public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue) {
- this.arrayBlockingQueue = arrayBlockingQueue;
- }
-
- @Override
- public void run() {
- while (true){
- try {
- String food = arrayBlockingQueue.take();
- System.out.println("从阻塞队列拿到了一个"+food);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- //测试类
- import java.util.concurrent.ArrayBlockingQueue;
-
- public class Test {
- public static void main(String[] args) {
- ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
-
- Cooker c = new Cooker(arrayBlockingQueue);
- Foodie f = new Foodie(arrayBlockingQueue);
-
- c.start();
- f.start();
- }
- }
3.6线程的状态
线程的状态有6种,分别是:
new:Thread对象new好之后的状态
runnable:Thread对象调用.start方法后
blocked:阻塞
waiting:等待
timed_waiting:计时等待
terminated:死亡
3.7线程池
3.7.1概念
线程池指存放线程的容器,大大提高了效率,不需要每需要执行一个任务就创建一个线程,执行完成后销毁线程;线程池的存在可以使线程任务结束后回到线程池,下次任务执行再直接使用
3.7.2Executors默认线程池
- Executors:
- static ExecutorService newCachedThreadPool();
- 这是一个静态方法,可以通过Executors这个类直接调用,返回一个ExecutorService对象
- ExecutorService提供了这些方法:
- submit(Runable r);//接收一个Runable接口的实现类,可以使用Lambda表达式
- submit(Callable c);//接收一个Callable接口的实现类,可以使用Lambda表达式
- shutdown();//摧毁线程池
3.7.3Executors创建指定上限的线程池
要想创建指定线程池容量的方法:
- ExecutorService newFixedThreadPool(int nTheads);
- ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
- ScheduledExecutorService newSingleThreadScheduledExecutor()
- ExecutorService newSingleThreadExecutor()
3.7.4自己直接创建线程池
上面的创建都是先创建Service,我们也可以直接创建线程池对象:
- public ThreadPoolExecutor(
- int corePoolSize,//核心线程数量 2
- int maximumPoolSize,//最大线程数量 5
- long keepAliveTime,//空闲线程最大的空闲时间
- TimeUnit unit,//空闲时间单位
- BlockingQueue<Runnable> workQueue, //阻塞队列,存放等待执行的任务
- ThreadFactory threadFactory,//造线程的工厂
- RejectedExecutionHandler handler//任务超过最大线程数+阻塞队列容量,拒绝的策略)
3.7.4.1拒绝策略
当任务超过最大线程数+阻塞队列容量会被拒绝,默认的拒绝策略是丢弃任务,并抛出异常
3.7.4.2非默认任务拒绝策略
- ThreadPoolExecutor.DiscardPolicy:
- *丢弃任务;
- ThreadPoolExecutor.DiscardOldestPolicy:
- *丢弃任务队列中等待时间最长的任务,再把最新的任务添加到队列中
- ThreadPoolExecutor.CallerRunsPolicy:
- *谁提交的这个任务,谁来执行这个任务;
附上线程池相关代码:
- public class MyThreadPoolDemo1 {
- public static void main(String[] args) {
- //创建线程池对象
- ExecutorService executorService = Executors.newCachedThreadPool();
- //使用Lambda表达式来创建函数式接口的对象
- executorService.submit(()->{
- System.out.println(Thread.currentThread().getName()+"正在运行");
- });
-
-
- //使用Lambda表达式来创建函数式接口的对象
- executorService.submit(()->{
- System.out.println(Thread.currentThread().getName()+"正在运行");
- });
-
- executorService.submit(()->{
- System.out.println(Thread.currentThread().getName()+"正在运行");
- });
-
- executorService.shutdown();
-
-
- }
- }
上边是通过Executors默认创建线程池,下边是自己new的线程池:
- public class MyThreadPoolDemo3 {
- public static void main(String[] args) {
- ThreadPoolExecutor pool = new ThreadPoolExecutor(
- 3,//核心线程数量
- 5,//总线程数量
- 2,//临时线程的空闲时间
- TimeUnit.SECONDS,//空闲时间单位
- new ArrayBlockingQueue<>(10),//阻塞队列
- Executors.defaultThreadFactory(),//创建线程的线程工厂
- new ThreadPoolExecutor.AbortPolicy());//当等待的线程超过总量+阻塞队列时的拒绝策略
-
- pool.submit(()->{
- System.out.println(Thread.currentThread().getName()+"RUNNING....");
- });
- pool.shutdown();
- }
- }
3.8volatile-问题
volatile关键字主要解决了内存不可见问题,就是当多个线程同时操作共享数据时,如果其中一个修改了共享数据,另外的线程无法及时获取到新的数据。
使用volatile关键字修饰变量后,那么有关该变量操作的代码,就不会被机器指令替换
内存不可见问题:jvm在执行java代码时,针对反复执行的代码,就会把这些代码标记为热点代码,这些热点代码在执行指定量次(10000),jvm会使用jit及时编译,把之前这些class指令替换为机器指令,反复执行的时候就再也不会读取新的数据了
也可以使用synchronized解决内存不可见问题,因为synchronized中的代码会强制查看共享内存中的数据;
3.9原子性
原子性指不可分割,举例:count++
count++虽然看起来是一行代码,但是执行的时候对应3个指令:
1.读取共享内存数据到变量副本;
2.对变量副本中的数据进行自增1;
3.把变量副本中的数据写入到共享内存;
所以当多个线程执行时,count++可能造成线程安全问题,
可以使用synchronized解决原子性导致的线程安全问题,将count++变成不可分的,但是synchronized是重量级锁,性能较低,也可以使用volatile+cas算法解决线程安全问题。
3.9.1AtomicInteger
jdk在并发包中提供了大量的Atomicxxx类,专门用于在多线程环境下编程,AtomicInteger是比较常用的一种:
- AtomicIneger();
- int get(); //获取值
- int getAndIncrement();//以原子方式将当前值加1,这里返回的是自增前的值
- int incrementAndGet();// 以原子方式将当前值加1,这里返回的是自增后的值
- int addAndGet(int delta);//以原子方式将参数与对象中的值相加,并返回结果
- int getAndSet(int delta);//以原子方式设置为newValue的值,并返回旧值。
3.9.2AtomicInteger-内存解析
volatile关键字加CAS算法可以解决内存不可见问题
CAS算法原理:在对共享数据进行操作时,把原来的旧值记录下来,在自己的栈内对数据进行操作之后要写入内存之前,比较现在内存中的值跟原来的旧值一样,那么说明没有其他线程对共享数据进行操作,那么对其进行修改(将修改的值写入内存);如果旧值不一样了,那么说明有其他线程对共享数据进行过其他操作了,那么需要再次获取内存的新值,再次进行操作,这个重新获取就是自旋。
- 伪代码:
- while(true){
- ①读取内存值,赋值给旧值;
- ②给旧值加1,得到新值;
- ③if(旧值==内存值){
- 内存值=新值;
- break;
- }
- }
3.10并发工具类
3.10.1HashTable
HashTable是线程安全的,因为它的底层方法都加了synchronized关键字,在操作共享数据时,将整个hash表都锁
3.10.2并发工具类-ConcurrentHashMap
ConcurrentHashMap继承了HashMap,所以父类的方法都是可以使用的
ConcurrentHashMap在JDK1.7中,有一个长度为16的不可变的数组,在每个数组位置上有一个可扩容小数组,该位置存储的数据超过小数组的加载因子,则自动扩容为原长度的2倍
ConcurrentHashMap在JDK1.8中,采用了 CAS + synchronized 来保证并发安全性
put方法:
1.计算key的hash值
2.如果当前table还没有初始化先调用initTable方法将tab进行初始化
3.tab中索引为i的位置的元素为null,则直接使用CAS将值插入即可
4.当前正在扩容
6.若当前为红黑树,将新的键值对插入到红黑树中
7.插入完键值对后再根据实际大小看是否需要转换成红黑树
8.对当前容量大小进行检查,如果超过了临界值(实际大小*加载因子)就需要扩容
3.10.3ConcurrentHashMap的高性能
在操作数据时只锁住索引处的链表,不像HashTable锁住一整张表
3.10.4并发工具类-CountDownLatch
如果一个线程需要等待其他多个线程执行完毕以后才能执行,可以使用CountDownLatch
public CountDownLatch(int count)://让当前线程等待,计数器为0时唤醒
public void await():
public void countDown():
附上完整代码:
- //这里写了一个妈妈要等待孩子都吃完饺子才收拾碗筷的案例
- import java.util.concurrent.CountDownLatch;
- //孩子线程
- public class ChildrenRunnable implements Runnable{
- private CountDownLatch countDownLatch;
- private int number;
-
- public ChildrenRunnable(CountDownLatch countDownLatch, int number) {
- this.countDownLatch = countDownLatch;
- this.number = number;
- }
-
- @Override
- public void run() {
- for (int i = 1; i <= number; i++) {
- System.out.println(Thread.currentThread().getName()+"正在吃第"+i+"个饺子");
- }
- countDownLatch.countDown();
- }
- }
- import java.util.concurrent.CountDownLatch;
- //妈妈线程
- public class MotherRunnable implements Runnable{
- private CountDownLatch countDownLatch;
- public MotherRunnable(CountDownLatch countDownLatch) {
- this.countDownLatch = countDownLatch;
- }
-
- @Override
- public void run() {
- try {
- countDownLatch.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("妈妈去收拾碗筷了");
- }
- }
- import java.util.concurrent.CountDownLatch;
-
- public class MyCountDownLatchDemo {
- public static void main(String[] args) {
-
- CountDownLatch countDownLatch = new CountDownLatch(3);//这里表示有三个线程要等待
- MotherRunnable mr = new MotherRunnable(countDownLatch);
-
- new Thread(mr).start();
-
- new Thread(new ChildrenRunnable(countDownLatch,7),"小黑").start();
- new Thread(new ChildrenRunnable(countDownLatch,9),"小红花").start();
- new Thread(new ChildrenRunnable(countDownLatch,11),"小狗").start();
- }
- }
3.10.5并发工具类-Semaphore
限制资源的执行量时 使用Semaphore
public Semaphore(int permits)
public void acquire()//获取信号量----阻塞方法
public void release()//释放信号量
完整代码:
- //这里模拟一个路口同时只允许三辆车通过,必须拿到通行证才能通过,通过后归还通行证,然后别的车才能拿到通行证
- public class MySemaphoreRunnable implements Runnable{
- //获取管理员对象
- private Semaphore semaphore = new Semaphore(3);
- @Override
- public void run() {
- try {
- semaphore.acquire();//相当于获取通行证
- System.out.println("拿到通行证,可以通行");
- Thread.sleep(2000);
- System.out.println("归还通行证");
- semaphore.release();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public class MySemaphoreDemo {
- public static void main(String[] args) {
- MySemaphoreRunnable msr = new MySemaphoreRunnable();
- for (int i = 0; i < 100; i++) {
- new Thread(msr).start();
- }
- }
- }
原文链接:https://blog.csdn.net/liuhang_1996/article/details/114677232
作者:Hdhhd
链接:http://www.javaheidong.com/blog/article/114285/5b518fa69e3134f9e57f/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!