网站开发综合实训,智慧团建密码格式是几位,得到app官网,织梦网站301跳转怎么做目录
1 多线程
1.1 基本概念
1.2 创建线程的三种方式
1.4 解决线程安全问题的三种方法
1.5 线程通信
1.6 线程状态
2 线程池
2.1线程池的概念
2.2 创建并提交任务
3 可见性
3.1 变量不可见性
3.2 变量不可见性的解决方案
4 原子性
4.1 原子性的概念
4.2 保证原…目录
1 多线程
1.1 基本概念
1.2 创建线程的三种方式
1.4 解决线程安全问题的三种方法
1.5 线程通信
1.6 线程状态
2 线程池
2.1线程池的概念
2.2 创建并提交任务
3 可见性
3.1 变量不可见性
3.2 变量不可见性的解决方案
4 原子性
4.1 原子性的概念
4.2 保证原子性的方案
4.3 原子类的CAS机制
5 多线程的并发包
5.1 ConcurrentHashMap类 5.2 CountDownLatch类
5.3 CyclicBarrier类
5.4 Semaphore类
5.5 Exchanger类 1 多线程
1.1 基本概念 程序(program)为了完成特定的任务使用某种语言编写的一组指令的集合也就是一段静态的代码。 进程(process)程序加载到内存中的一次执行过程或者是正在运行中的一个程序。进程作为资源分配的单位系统在运行时会为每个进程分配不同的内存区域。 线程(thread)一个进程可被进一步细化成一个或多个线程线程就是一个程序内部的一条执行路径。如果一个程序可以同时并行执行多个线程我们就称它是支持多线程的。线程作为调度和执行的单位每个线程都拥有独立的运行栈和程序计数器所有的线程共享进程分配的堆和方法区都从同一个堆中分配对象访问相同的变量和对象。这就是的线程之间的通信更加简便、高效但是由于共享系统资源也就带来了安全隐患。 单CPU与并发CPU相当于人的大脑用来动态的为程序的运行分配内存空间之所以是动态的是因为一个CPU一次只能执行一个进程但是同一时间一台电脑几乎不可能只开启一个进程一个CPU会不停的切换执行多个进程也就是并发执行由于切换的速度比较快在人类看来计算机就是在同时执行多个进程。 多CPU与并行多CPU是相对于单CPU而言的概念多CPU就是多个CPU同时执行不同的进程也就是并行执行与此同时每个CPU还会不停的切换执行多个进程也就是并发执行。 并发与并行举个例子比如说今年暑假的抗洪救灾现场需要将装成袋的沙子搬到决堤口挡水并发这里有20袋沙子(相当于20个进程)但是只有一个人来搬(单CPU)这个人搬完一袋换一袋由于换的速度比较快看起来就好像是20袋沙子一块被搬一样。并发和并行同步执行这里有20袋沙子(相当于20个进程)但是有四个人来搬(多CPU)四个人同时搬就是并行这四个人搬完各自的一袋换一袋由于换的速度比较快看起来也好像是20袋沙子一块被搬一样这里的每个人搬完换另一袋就叫并发。于是大部分情况下的单CPU的性能要优于多CPU的。 一个java应用程序java.exe至少应该包三个线程main()主线程、gc()垃圾回收线程、异常处理线程。
1.2 创建线程的三种方式
方法一继承Thread类 四步创建类并继承Thread--重写run方法--创建线程对象--调用start方法 ⚠ 创建线程对象调用start方法才会产生新的线程(start方法底层会先向CPU注册线程在调用run方法)如果调用run方法会被当做是一个普通类执行这样进程里面也就还只有一个主线程。 ⚠ main方法里面要先创建子线程出来再分配主线程的任务否则在进程执行的时候会认为只有一个主线程因为从代码的执行顺序来看此时还没有创建子线程从而会导致永远都是先执行完主线程任务再执行子线程任务。 这样创建线程的优点是编码简单缺点是通过继承Thread类创建线程会导致线程类无法在对其他类进行继承功能无法通过继承来拓展(单继承的局限性) Thread的常用API 方法二实现Runnable接口 五步创建任务类并实现Runnable接口--重写run方法--创建任务对象--将任务对象包装成线程对象--调用start方法 这个方法创建线程的缺点比上一种方法多了一步下一个方法可以获取重新写call方法的返回结果而这个的run方法没有返回值。优点有由于任务类没有继承任何类可以继续继承其他类拓展功能同一个任务类可以被包装成多个线程对象适合多个线程共享同一个资源实现解耦操作任务可以被多个线程共享任务与任务之间有相互独立不影响 创建线程的简化写法(匿名内部类) 方法三实现Callable接口 六步创建任务类并实现Callable接口--重写call方法--创建任务对象--将任务对象包装成FutureTask对象--将FutureTask对象包装成线程对象--调用start方法 第三种方法和第二种方法的差别就是这个方法可以获取返回值 1.3 线程安全问题 当多个线程操作同一个共享资源的时候就有可能会出现线程安全问题。比如说小明和小红有一个共同情侣账户里面有100块钱小明和小红同时登录系统取钱会出现以下情况
线程号人员操作结果账户余额1小明查询余额100?true1002小红查询余额100?true1003小明取100100-10004小红取1000-100-100
由于线程的执行时随机且无法回退的所以可能会导致两人都查询账户余额有100块的情况线程继续往后执行就会导致账户被两次取钱成为负值这肯定是有问题的。 账户bean类 主类 取钱任务类 控制台运行结果 1.4 解决线程安全问题的三种方法
方法一同步代码块 synchronized(锁对象) { 访问共享资源的核心代码; } ⚠ 在实例方法中建议使用this作为锁对象静态方法中建议使用类名.class作为锁对象 方法二同步方法 在方法的定义时使用synchronized修饰即可 同步方法与同步代码块的方法差不多同步方法的底层是将整个方法都锁了起来 方法三Lock显式锁 创建锁对象 上锁 解锁 使用该方法上锁的话尽量要按照这种try-catch-finally的方式否则可能遇到上锁之后出现异常此时程序就无法继续运行也就是说永远无法解锁导致出现问题。 1.5 线程通信 现在有这么一个需求 使用IDEA进行实现代码 账户bean类 主类 取钱任务类 存钱任务类 控制台运行结果 这是个死循环运行了一会就暂停了截图 1.6 线程状态 ⚠ sleep方法只是计时等待不会把锁放开wait方法是把锁放开进入等待。 死锁 死锁就是不同的线程同时分别占用着对方需要的锁不放都在等待着对方放锁。出现死锁之后不会产生任何的异常和提示只是所有的线程都处于阻塞状态无法继续。 死锁产生的四个必要条件
互斥使用即共享资源一次只能被一个线程使用不可抢占即线程不能从正在使用共享资源的线程手中夺取资源请求保持一个线程在请求另一个线程资源的同时依然占有着那个线程所请求的资源循环等待1要2的资源2要1的资源形成了一个循环等待
2 线程池
2.1线程池的概念 前面讲过每当我们需要使用线程的时候不管是使用哪个方法都需要去创建一个线程实现起来并不难但是会产生一个问题如果并发的线程数量很多且线程的执行时间都很短的时候线程的创建和销毁都需要时间频繁的创建销毁线程就会导致系统的效率大大降低。 解决以上问题就用到了线程池的概念线程池就是一个可以容纳固定多个线程的容器线程池中的线程可以反复使用。线程池中工作线程(PoolWorker)的个数是固定的而任务接口(Task)想要使用工作线程的话就需要在任务队列(TaskQueue)中排队等待任务执行完毕之后工作线程归还线程池出于空闲状态。
2.2 创建并提交任务 创建线程池并指定线程数量 无返回值的Runnable任务 有返回值的Callable任务 ⚠ 线程池对象调用submit(任务对象)方法将任务对象提交给线程池执行线程池在执行完所有的任务之后并不会直接关闭而是处于等待状态等待其他任务的使用如果没有其他任务就一直处于等待状态可以调用shutdown()方法等待任务执行完毕之后关闭线程池。 3 可见性
3.1 变量不可见性 首先Java专门为多线程定义了一种Java内存模型(Java Memory Model JMM)这种内存模型要不同于单线程的内存模型JVM。JMM描述了Java程序中各种共享变量的访问规则以及在JVM中将变零存储在内存中和从内存中读取像变量的底层细节。
JMM的规定
所有的共享变量都存储于主内存。这里的变量指的是实例变量和类变量并不包含局部变量因为局部变量是线程私有的不存在竞争问题。每个线程有自己的工作内存里面存放的是从主内存中拷贝来的共享变量副本。线程对变量的所有操作都在线程的工作内存中完成而不是直接操作主内存中的共享变量。不同线程之间也不能访问对方的工作内存线程间变量的值传递通过主内存中转完成。不可见性描述 并发编程下也就是说当存在多个线程访问一个共享资源时一个线程改变了这个资源的变量值但是其他线程并不能看到这个变量值的改变读取到的依然是变量修改之前的值。以上现象又被称为是多线程间变量的不可见性
变量不可见性的原理 3.2 变量不可见性的解决方案
方案一加锁 对线程任务进行加锁。其底层原理在于线程在获得锁对象之后会清空线程的工作内存从主内存中再次拷贝共享变量的值成为共享变量副本此时线程工作内存中共享变量副本就是最新的变量值了。
方案二volatile关键字修饰 定义变量的时候使用volatile关键字进行修饰。其底层原理与加锁不同的是volatile关键字是在主内存发现有线程对共享变量的值进行修改之后通知其他线程工作内存中的共享变量副本的值失效其他线程在访问共享变量的副本的时候发现值已失效于是重新拷贝共享变量至工作内存中。
4 原子性
4.1 原子性的概念 原子性指的是一批操作是一个整体要么同时成功要么同时失败不能被其他干扰。volatile只能保证线程之间变量的可见性但是不能保证变量操作的原子性。
4.2 保证原子性的方案
方案一加锁 加锁就是对线程任务进行加锁。加锁不仅能够保证线程的原子性还能保证线程之间变量值修改的可见性。但是加锁会降低程序的性能故又有了第二种方法。
方案二原子类
Java提供了java.util.concurrent.atomic包(简称atomic包)包里面有各种类类中有很多方法。 原子类包含有很多种其中包括AtomicInteger、AtomicDouble……对不同数据类型的数据进行操作更新的类这些类中定义了一些API去代替运算与普通运算方式的区别在于这种方法的运算能在不加锁的情况下保证线程的原子性。
原子类的使用
原子类中定义了很多的API根据自己的需求选择使用 从上图中可以看出来一个线程执行完所有的任务下一线程再执行这种模式与前面的上锁很像其实原子类就是加锁机制的高性能版本在实现加锁机制保证线程安全的同时又保证了原子性。
4.3 原子类的CAS机制 CAS的全称为Compare And Swap译为先比较再交换CAS可以将read-modify-check-write操作转换为原子操作保证了线程的原子性。CAS机制不锁任务任意线程的任何时候都可以操作任务就是操作完任务之后要将操作前的共享变量副本与主内存中的共享变量值进行对比一致的话就修改主内存中共享变量的值不一致的话就将之前的任务操作作废重新开启一次任务(拷贝、修改、对比)。 5 多线程的并发包
5.1 ConcurrentHashMap类
java.util.concurrent.ConcurrentHashMap
在创建HashMap集合的时候使用即可创建之后即可保证线程安全类下面的API操作和HashMap一样正常使用即可。 5.2 CountDownLatch类
java.util.concurrent.CountDownLatch
创建一个计数器用于实现线程执行时的计数等待使用有参构造创建对象的同时给定计数步数也就是说等待计数器减几次await()方法让当前线程让出CPU等待计数器的值清零countDown()方法可以将计数器的值减1 5.3 CyclicBarrier类
java.util.concurrent.CyclicBarrier
创建一个循环屏障对象传入两个参数阻挡线程个数和一个Runnable任务对象意思就是屏障阻挡了相应的线程个数之后就执行这个Runnable任务 5.4 Semaphore类
java.util.concurrent.Semaphore Semaphore对象的主要作用就是控制线程并发的数量也就是说使用有参构造创建一个Semaphore对象设置最大允许进入acquire()方法和release()方法之间任务代码的线程个数。可以用来限制一个资源同一时间的的最大访问人数使用synchronized上锁相当于创建Semaphore对象的时候传参为1。 5.5 Exchanger类
java.util.concurrent.Exchanger
Exchanger类适用于线程间协作通信的类利用构造器定义一个Exchanger对象容器使用泛型规定容器暂存数据的类型可以是无参构造器也可以是有参构造器两个参数超时不再交换时间和超时时间单位调用exchange(V x)方法进行数据交换并返回交换之后对方传过来的结果。