多线程简单总结

多线程总结

1.多线程的概念

1.1初步了解多线程

1.什么是多线程?

采用多线程技术可以同时执行多个任务(比如:迅雷同时下载多个任务),多线程需要硬件支持

1.2并发和并行

1.什么是并行?

在同一时刻,有多条线程在多个CPU上同时执行

2.什么是并发?

在一段时间内,有多条线程在单个CPU上交替执行

1.3进程和线程

1.什么是进程?

可以理解为正在运行的程序

2.什么是线程?

线程它是进程的一部分,是进程中的单个控制流,是一条执行路径

一个进程,可以有多条线程,至少有一条线程(线程才是CPU执行的最小单元)

线程只能在进程中运行

3.为什么要学习多线程

可以让程序同时执行多个任务,提高程序的执行效率(例如一次上传多张图片,同时打开多个网页)

1.4多线程的实现方式

重点用实现方式,直接继承不反对但是不推荐主要记后两个**

1.实现多线程的方式有哪些?

A 继承Thread类的方式进行实现

继承Thread:类名 extends Thread

B:实现Runnable接口的方式进行实现

实现Runnabe接口:implements Runnable,并且重写接口中的run方法

C:利用Callable和Future接口方式实现(有返回值)

实现Callable接口:implement Callable<指定泛型>,再重写接口中的Call方法,而且指定的泛型要和返回值保持一致

创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

创建Thread类的对象,把FutureTask对象作为构造方法的参数

再调用get方法,就可以获取线程结束之后的结果。

1.5三种方式对比

A:继承Thread类

  • 好处: 编程比较简单,可以直接使用Thread类中的方法

  • 缺点: 可以扩展性较差,不能再继承其他的类

B:实现Runnable、Callable接口

  • 好处: 扩展性强,实现该接口的同时还可以继承其他的类

  • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

1.6三种实现方式的应用场景

记住了没有返回值用Runnable接口,有返回值用Callable接口即可

 

2.线程类

3.1设置和获取线程名称

setName(String name) 将此线程的名称更改为等于参数name

getName() 返回此线程的名称

3.2获得当前线程对象

Thread currentThread() 返回对当前正在执行的线程对象的引用

Thread t = new Thread();

创建线程对象,可以传参数

3.线程调度**

3.1线程休眠

sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

Thread.sleep(100);

使线程休眠100毫秒

 

3.2设置线程优先级

setPriority(int newPriority)

设置线程优先级

最高是10,默认为5

 

3.3设置守护线程

守护线程是程序运行的时候在后台提供一种通用服务的线程(保镖与主人)

被守护的线程结束,不会立即结束,挣扎一会儿才结束

void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

 

4.线程同步

4.1synchronized**(同步代码块)

A格式:

synchronized(任意对象) { 多条语句操作共享数据的代码 }

B特点:

默认是打开的,只要有一条线程进去执行了,锁就会自动关闭,线程出来之后就会自动打开

C 锁对象必须唯一吗?

数据之间必须要使用同一把锁,才能保证共享数据的安全

不同线程如果锁的不是同一个对象,就解决不了线程的安全问题

D 什么情况下会有安全问题:

1 多线程环境

2 有共享数据

3 有对共享数据的操作(增、删、改(除了long、double类的基本数据类型直接赋值)、查)

4.2synchronized**(同步方法)

A格式:

就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体;}

B同步方法的锁对象是什么呢?

this

C 静态同步方法

同步静态方法:就是把synchronized关键字加到静态方法上

修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }

D 同步静态方法的锁对象是什么呢?

类名.class

4.3Lock

1.如何使用Lock?

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

4.4synchronized与Lock的区别

Lock:手动上锁

synchronize:自动上锁

4.5如何选择

大部分情况会选择synchronized,开发首选

4.6什么情况下会出现线程安全问题

不同线程如果锁的不是同一个对象,就解决不了线程的安全问题

4.7死锁

什么是死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

什么情况下会产生死锁

互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

5.线程间的通信

5.1wait()

作用和特点

导致当前线程等待同时释放锁,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法

wait和sleep区别

wait,会使线程等待,同时释放锁

sleep,会让线程休眠一会但是不会释放锁

5.2notify()或notifyAll()

notify()作用和特点

唤醒正在等待单个线程,并不立即释放锁

notifyAll()作用和特点

唤醒正在等待所有线程,并不立即释放锁

注意事项

notify()和notifyAll()只会唤醒一个或者多个锁,并不会释放锁

6.阻塞队列

6.1什么是阻塞队列

阻塞队列是一个在队列基础上又支持了两个附加操作的队列

6.2附加的方法

put(anObject): 将参数放入队列,如果放不进去会阻塞

take(): 取出第一个数据,取不到会阻塞

6.3常见BlockingQueue实现类

1.ArrayBlockingQueue

特点: 底层是数组,有界

2.LinkedBlockingQueue

特点: 底层是链表,无界.但不是真正的无界,最大为int的最大值

7.线程状态介绍

7.1Java中规定了线程有哪几种状态

新建、就绪、阻塞、等待、计时等待、死亡

8.线程池

8.1什么是线程池

存线程的容器就是线程池

8.2线程池的原理

创建一个池子,池子中是空的

有任务需要执行时,才会创建线程对象

当任务执行完华,线程对象归还给池子

8.3为什么要使用线程池

避免了频繁创建线程,进而提高系统的效率

8.4如何创建线程池

重点记第二个

1.Executors

static ExecutorService newCachedThreadPool() 创建一个默认的线程池

static ExecutorService newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

2.ThreadPoolExecutor

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

8.5ThreadPooleExecutor构造参数有哪些,都代表什么意思?

参数一:核心线程数量

参数二:最大线程数

参数三:空闲线程最大存活时间

参数四:时间单位---TimeUnit

参数五:任务队列 --- 当线程数超过核心线程数量小于最大线程数时,让任务在队列 中等着,等有线程空闲了,再从这个队列中获取任务并执行

参数六:创建线程工厂 --- 按照默认的方式创建线程对象

参数七:任务的拒绝策略 --- 1.什么时拒绝? 需要同时执行的任务数量 > 池子中最 大的线程数量+队列的容量

9.线程安全

9.1线程安全-可见性

着重记忆第二个

volatile如何实现内存可见性

(1)线程写volatile变量的过程: JMM会把该线程对应的工作内存(本地内存)中的共享变量值刷新到主内存

(2)线程读volatile变量的过程: JMM会把该线程对应的工作内存(本地内存)置为无效。线程接下来将从主内存中读取共享变量

synchronized如何实现内存可见性

(1) 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要时从主内存中重新读取最新的线程值

(2) 线程释放锁时,必须把共享变量的最新值刷新到主内存中

9.2线程安全-原子性

什么是原子性

(送冰淇淋案例,会少执行几次)

所谓的原子性就是完成功能的所有操作要么都执行,要么都不执行

volatile关键字不能保证原子性

java中如何保证原子性操作

使用锁

利用原子类

什么是原子操作类

原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。

AtomicInteger的常用方法:

int get(): 获取值 // int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。 // int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。 // int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。 // int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。

什么是CAS算法

 

9.3悲观锁和乐观锁

synchronized和CAS的区别

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有 可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)

CAS:是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值。如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

经验

jdk1.6之后对synchronized(偏向锁(根本就不加锁)、轻量级锁(CAS),重量级锁(悲观锁))进行了大量的优化

10.并发工具包

10.1并发容器(Hashtable)

为什么要使用Hashtable

HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

Hashtable的原理 特点

Hashtable采取悲观锁synchronized的形式保证数据的安全性

只要有线程访问,会将整张表全部锁起来,所以Hashtable效率低下

10.2并发容器(ConcurrentHashMap)

为什么要使用ConcurrentHashMap

为了保证数据的安全性也要考虑效率的情况下,JDK1.5以后所提供ConcurrentHashMap。

什么是ConcurrentHashMap

体系结构 :

三种Map结构的对比

1 ,HashMap是线程不安全的。多线程环境下会有数据安全问题

2 ,Hashtable是线程安全的,但是会将整张表锁起来,效率低下

3,ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。

10.3并发工具类

CountDownLatch

应用场景:

让某一条线程等待其他线程执行完毕之后再执行

妈妈包饺子,孩子们吃完,妈妈收拾案例

代码实现 :

package com.itheima.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread1 extends Thread {

   private CountDownLatch countDownLatch;
   public ChileThread1(CountDownLatch countDownLatch) {
       this.countDownLatch = countDownLatch;
  }

   @Override
   public void run() {
       //1.吃饺子
       for (int i = 1; i <= 10; i++) {
           System.out.println(getName() + "在吃第" + i + "个饺子");
      }
       //2.吃完说一声
       //每一次countDown方法的时候,就让计数器-1
       countDownLatch.countDown();
  }
}
package com.itheima.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread2 extends Thread {

   private CountDownLatch countDownLatch;
   public ChileThread2(CountDownLatch countDownLatch) {
       this.countDownLatch = countDownLatch;
  }
   @Override
   public void run() {
       //1.吃饺子
       for (int i = 1; i <= 15; i++) {
           System.out.println(getName() + "在吃第" + i + "个饺子");
      }
       //2.吃完说一声
       //每一次countDown方法的时候,就让计数器-1
       countDownLatch.countDown();
  }
}
package com.itheima.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread3 extends Thread {

   private CountDownLatch countDownLatch;
   public ChileThread3(CountDownLatch countDownLatch) {
       this.countDownLatch = countDownLatch;
  }
   @Override
   public void run() {
       //1.吃饺子
       for (int i = 1; i <= 20; i++) {
           System.out.println(getName() + "在吃第" + i + "个饺子");
      }
       //2.吃完说一声
       //每一次countDown方法的时候,就让计数器-1
       countDownLatch.countDown();
  }
}
package com.itheima.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class MotherThread extends Thread {
   private CountDownLatch countDownLatch;
   public MotherThread(CountDownLatch countDownLatch) {
       this.countDownLatch = countDownLatch;
  }

   @Override
   public void run() {
       //1.等待
       try {
           //当计数器变成0的时候,会自动唤醒这里等待的线程。
           countDownLatch.await();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       //2.收拾碗筷
       System.out.println("妈妈在收拾碗筷");
  }
}
package com.itheima.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class MyCountDownLatchDemo {
   public static void main(String[] args) {
       //1.创建CountDownLatch的对象,需要传递给四个线程。
       //在底层就定义了一个计数器,此时计数器的值就是3
       CountDownLatch countDownLatch = new CountDownLatch(3);
       //2.创建四个线程对象并开启他们。
       MotherThread motherThread = new MotherThread(countDownLatch);
       motherThread.start();

       ChileThread1 t1 = new ChileThread1(countDownLatch);
       t1.setName("小明");

       ChileThread2 t2 = new ChileThread2(countDownLatch);
       t2.setName("小红");

       ChileThread3 t3 = new ChileThread3(countDownLatch);
       t3.setName("小刚");

       t1.start();
       t2.start();
       t3.start();
  }
}

原理:

public CountDownLatch(int count) 参数传递线程数,表示等待线程数量。并定义一个计数器

public void await() 让线程等待,当计数器为0时,会唤醒等待的线程

public void countDown() 当前线程执行完毕时,会将计数器-1

Semaphore

应用场景

可以控制访问特定资源的线程数量。

汽车过路口案例,定义一次只能过几辆,过去之后马上归还通过卡

代码实现 :

package com.itheima.mysemaphore;

import java.util.concurrent.Semaphore;

public class MyRunnable implements Runnable {
   //1.获得管理员对象,
   private Semaphore semaphore=new Semaphore(2) ;


   @Override
   public void run() {
       //2.获得通行证
       try {
           semaphore.acquire();
           //3.开始行驶
           System.out.println("获得了通行证开始行驶");
           Thread.sleep(2000);
           System.out.println("归还通行证");
           //4.归还通行证
           semaphore.release();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }
}
package com.itheima.mysemaphore;

public class MySemaphoreDemo {
   public static void main(String[] args) {
       MyRunnable mr = new MyRunnable();

       for (int i = 0; i < 100; i++) {
           new Thread(mr).start();
      }
  }
}

 

原理

  • Semaphore是通过一个计数器(记录许可证的数量)来实现的,计数器的初始值为需要等待线程的数量

  • eg:Semaphore s = new Semaphore(10); // 线程最大的并发数为10

  • 线程通过acquire()方法获取许可证(计数器的值减1),只有获取到许可证才可以继续执行下去,否则阻塞当前线程

  • 线程通过release()方法归还许可证(计数器的值加1)

posted @ 2021-01-04 16:22  java菜鸟儿  阅读(209)  评论(0编辑  收藏  举报