多线程
多线程
多个线程并发
1 早期单核心单线程
2 双核心四线程
3 四核心八线程
4 八核心八线程
并行同时执行
并发交替执行
进程 正在运行的程序
买电脑
内存 32G
硬盘 1T固态
CUP 英特尔 amd×
显卡:集成显卡 独立
一实现多线程的三种方式
1 继承Thread类 重写run()方法 ( 里面写线程开启后执行的方法)
2 实现Runnable接口 重写run()方法 是Threa类实现的接口
3 实现Callable接口,重写call() , 具备返回。
FutureTask类 Runnable接口的孙子
FutureTask类中的get() 方法得到 call()的返回值 ,但要在线程执行结束后使用,否则死锁
二 Thread 类
上面的的三种方法,执行线程都需要Thread类来执行,因为有多态机制,使得有了上面的的三种方法。但都是作为参 数传递给它的构造。
下面介绍Thread类中的几个常用成员方法
String getName() //获取线程的名字
void setName(String name)//设置线程的名字
static Thread currentThread() //获取当前执行的线程对象
sleep (long time) //线程休眠毫秒
start() //开启线程
public final void setPriority(int newPriority)//设置线程优先级
public final int getPriority() //得到线程优先级
public final void setDaemon(boolean b)/设置守护线程 true设置为守护线程 普通线程结束后,守护线程也会消失。
第一种 继承Thread类 线程默认名称 Thread-编号 Thread的成员方法getName获得名字
setName起名字
构造方法起名字 需要 子类有参构造,super(参数),不调用默认的super()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.yang.thread; public class MyThread extends Thread{ public MyThread(String name) { super (name); } public MyThread() { } @Override public void run(){ for ( int i = 0 ; i < 100 ; i++) { System.out.println(getName()+ "线程呢个开启了" +i); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.yang.thread; public class MyTest { public static void main(String[] args) { //创建一个线程对象 MyThread myThread= new MyThread( "小小" ); //开启一条线程 myThread.start(); //创建一个线程对象 MyThread myThread1= new MyThread( "大大" ); //开启第二条线程 myThread1.start(); } } |
第二种 实现接口的方法实现线程的改名方法
Thread类中
跟Thread类没有继承关系得类 Thread.currentThread().getName();//获取运行线程的名称
Thread(Runnable实现类对象,线程名字)构造方法
1 | Thread thread= new Thread(myRunnable, "黑羊羊" ); |
1 | Thread.currentThread().getName() |
Thread类方法
1 static void sleep(long time);指定线程休眠 毫秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Override public void run(){ while ( true ){ if (num<= 0 ){ break ; //卖完了 } else { try { <strong>Thread.sleep( 100 );</strong> } catch (InterruptedException e){ e.printStackTrace(); } } num--; System.out.println(Thread.currentThread().getName()+ "还剩" +num+ "票" ); } } |
2 线程调度模型
分时调度 轮流平均占用cup时间片
抢占式调度模型 优先级高的线程抢占CUP
java采用抢占式模型
setPriority(int )设置优先级 getPriority()获得优先级
3后台线程/守护线程
final void setDaemon(boolean b)/设置守护线程 true设置为守护线程 普通线程结束后,守护线程也会消失。
1 2 3 4 5 6 7 8 9 10 | public class Demo { public static void main(String[] args) { MyThread1 myThread1= new MyThread1( "将军" ); MyThread2 myThread2= new MyThread2( "士兵" ); //设置士兵线程为守护线程 myThread2.setDaemon( true ); myThread1.start(); myThread2.start(); } } |
三 线程的安全
1原因
多个线程访问共享数据,执行线程方法中的每一行代码,都会失去CPU的执行权力!
2 解决方案 多条语句操作共享数据代码块锁起来
1 同步代码块
2 同步方法
3 Lock
1 同步代码块 自动开自动关
synchronized(任意对象){ //多个线程必须使用同一把锁
多条语句操作共享数据的代码
}
第一种继承Thread 数据要加静态,
第二种实现Runnable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package com.yang.ticket; public class Ticket implements Runnable{ public int num= 100 ; private Object ticket= new Object(); @Override public void run(){ while ( true ){ try { Thread.sleep( 100 ); } catch (InterruptedException e){ e.printStackTrace(); } synchronized (ticket){ if (num<= 0 ){ break ; //卖完了 } else { } num--; System.out.println(Thread.currentThread().getName()+ "还剩" +num+ "票" ); } } } } |
同步方法 锁住方法中的所有代码
synchronized 返回值类型 方法名(){}
锁对象this
静态同步方法static synchronized 返回值类型 方法名(){}
锁对象 当前类名.class 当前类字节码对象
2 lock锁
void lock()获得锁
void unLock()开锁
ReentrantLock Lock接口的实现类。加锁解锁先创建该对象(ReentranLock)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.yang.Lock.MyLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticket implements Runnable{ private int num= 100 ; private ReentrantLock reentrantLock= new ReentrantLock(); @Override public void run() { while ( true ){ try { reentrantLock.lock(); if (num<= 0 ){ break ; } else { Thread.sleep( 100 ); num--; System.out.println(Thread.currentThread().getName()+ "票还有" +num+ "张" ); } } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } } } |
死锁
多个线程相互持有对方执行所需要的资源,互不相让,相互等待。
等待唤醒机制最基本的实现方式
生产者和消费者 多线程协作模式
Object类中的
wait() 让线程处于等待状态 等待状态允许别的线程执行和sleep不一样
notify() 叫醒一个处于等待的线程
notifyAll() 叫醒所有处于这把锁上等待的线程。
必须由锁对象调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Consumer extends Thread{ @Override public void run(){ while (Desk.num> 0 ) { synchronized (Desk.student) { if (Desk.flag== false ){ System.out.println(getName()+ "又做了一个汉堡" ); Desk.flag= true ; Desk.student.notifyAll(); } else { try { Desk.student.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Productor extends Thread{ @Override public void run(){ while (Desk.num> 0 ) { synchronized (Desk.student) { if (Desk.flag== true ){ System.out.println(getName()+ "我吃了一个汉堡还想吃" +(--Desk.num)); Desk.flag= false ; Desk.student.notifyAll(); } else { try { Desk.student.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } |
1 2 3 4 5 6 | public class Desk { public static boolean flag= false ; public static int num= 10 ; public static Student student= new Student(); } |
1 2 3 4 5 6 7 8 9 10 11 | public class MTest { public static void main(String[] args) { Consumer consumer= new Consumer(); Productor productor= new Productor(); consumer.setName( "消费者" ); productor.setName( "生产者" ); consumer.start(); productor.start(); } } |
阻塞队列
阻塞队列实现等待唤醒机制,代码实现更简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Foodie extends Thread{ private final ArrayBlockingQueue<String> arrayBlockingQueue; public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue) { this .arrayBlockingQueue = arrayBlockingQueue; } @Override public void run(){ while ( true ){ try { System.out.println( "吃了一个" +arrayBlockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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( "汉堡包" ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "厨师放了一个汉堡包" ); } } } |
1 2 3 4 5 6 7 8 9 | public class MyTest { public static void main(String[] args) { ArrayBlockingQueue<String> arrayBlockingQueue= new ArrayBlockingQueue<>( 1 ); Cooker cooker= new Cooker(arrayBlockingQueue); Foodie foodie= new Foodie(arrayBlockingQueue); cooker.start(); foodie.start(); } } |
BlockingQueue:
put();将参数放入队列,如果放不进去堵塞
take() ;返回取到的元素,取不出来等待
ArrayBlockingQueue(阻塞队列最多能放几个容量) 底层数组,有界
LInkedBliokingQueue() 底层链表,无界(int 的最大值)
1 2 3 4 5 6 7 8 9 | public class MyArrayBlockingQueue { public static void main(String[] args) throws InterruptedException { LinkedBlockingQueue<String> arrayBlockingQueue= new LinkedBlockingQueue<>( 1 ); arrayBlockingQueue.put( "汉堡包" ); System.out.println(arrayBlockingQueue.take()); arrayBlockingQueue.take(); System.out.println( "我结束了吗" ); } } |
阻塞队列和等待唤醒机制
线程处于运行状态(抢到CUP的执行权)和CPU产生关系,JVM没有定义线程的运行状态
线程池 存放经常用的线程
三种方式
1 创建一个默认的空池子 ,可以容纳 int 最大值创建使用Executors中的静态方法
ExecutorService控制线程池
Executors 帮助我们创建线程池对象
ExecutorService executorService=Executors.newCachedThreadPool(); //返回池子的控制者对象
新的线程任务,会查看线程池有没有空闲的线程对象,没有创建新的,有用线程池中的
2 submit 提交
3 shutdown 关闭线程池
executorService.shutdown()
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class MyExecutor { public static void main(String[] args) throws InterruptedException { //返回一个线程池的控制者对象 MyRunnable myRunnable= new MyRunnable(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+ "线程在执行了" ); }); Thread.sleep( 100 ); executorService.submit(myRunnable); executorService.shutdown(); } } |
2 创建一个指定最多线程数量的线程池
ExecutorService executorService=Executors.newFixedThreadPool(线程数量);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class MynewFixedThreadPool { public static void main(String[] args) { //返回一个固定长度的线程池 ExecutorService executorService = Executors.newFixedThreadPool( 2 ); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+ "线程池在执行" ); }); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+ "线程池在执行" ); }); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+ "线程池在执行" ); }); executorService.shutdown(); } } |
3 ThreadPoolExecutor
1正式员工数 不能小于0
2最大员工数 不能<0 >=核心数量
3临时员工空闲多久被辞退(值) 不能为null
4临时员工空闲多久被辞退(单位) 不能为null
5排队的客户
6 从哪里找人
7当排队人数过多,超出顾客下次再来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.yang.threadpool; import java.util.concurrent.*; public class MyThreadPool { public static void main(String[] args) { ThreadPoolExecutor pool= new ThreadPoolExecutor( 2 , //参数一 核心线程数量 5 , //最大线程数量 2 , //空闲线程最大存活时间 TimeUnit.DAYS, //时间单位--TimeUnit定义了时间单位 new ArrayBlockingQueue<>( 10 ), //任务队列--让任务排队,有线程空闲了在执行 Executors.defaultThreadFactory(), //创建线程工厂 new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略 超过排队数量+最大线程数量该怎末办 ); /* 特点 1 如果提交任务书小于等于核心线程的数量,都会创建核心线程 2 如果超过核心线程的数量,并且小于等于核心线程数量+排队数量,不会创建临时线程继续用核心线程。 3 超过核心线程数量,并且大于核心线程数量+排队数量开始创建临时工。 <br> 4 任务数量超过,最大线程数量+排队数量,开始执行默认的拒绝策略 <br> */ <br> }<br> } |
拒绝策略
AbortPlicy() 拒绝服务,抛出异常
DiscardPolicy() 不抛出异常直接抛弃
DiscardOldestPolicy()抛弃等待最久的任务,然后把当前任务加队列
CallerRunPolicy() 不采用线程池执行,采用其他线程执行
共享数据拷贝到线程内存中,如果线程一直使用(线程执行太快发现的几率小,慢了发现新的数据的几率大)这个数据,不会去获取最新的共享数据。
volatile 关键字解决 易变的 不稳定的 (但不能保证原子性)强制线程使用数据时,都会区查看共享数据的值
将共享数在线程的变量副本中临时存储,当另一个线程更改共享数据时,这个线程仍然使用之前的变量副本数据,也会去查看新的共享数据,但是无法控制
1 | public static volatile int money= 100000 ; |
1 | 易变的数据 |
1 2 3 4 5 6 7 8 9 10 11 12 | public class Boy implements Runnable{ @Override public void run() { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } Money.money= 1 ; } } |
1 2 3 4 5 6 7 8 9 | public class Girle implements Runnable{ @Override public void run() { while (Money.money== 100000 ){ } System.out.println( "不是十万了" ); } } |
1 2 3 | public class Money { public static volatile int money= 100000 ; } |
1 2 3 4 5 6 7 8 9 10 | public class MyTest { public static void main(String[] args) { Boy boy= new Boy(); Thread thread= new Thread(boy); thread.setName( "小皮" ); Girle girle= new Girle(); Thread thread1= new Thread(girle); thread1.setName( "小傻" ); thread.start(); thread1.start(); }} |
synchronized 同步代码块解决 强制我们去看共享数据的最新值
1 线程获得锁对象
2 清空自己变量副本中保留的值
3 拷贝共享变量最新值到变量副本
4执行代码
5 先修改变量副本中的值在赋值给共享数据
6 释放锁
原子性
多个操作要么同时成功,要么同时失败,多个操作是一个不可分割的整体(事务)
a++不具有原子性 a+1执行了,但在要赋值给共享数据的时候,cup执行权被其他线程抢走了,volatile不能保证原子性。
可以用synchronized
原子类类 Atomic+基本数据类型包装类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static void main(String[] args) { AtomicInteger atomicInteger= new AtomicInteger(); AtomicInteger atomicInteger1= new AtomicInteger( 10 ); System.out.println(atomicInteger); System.out.println(atomicInteger1); int i = atomicInteger1.incrementAndGet(); System.out.println( "先增加在返回" +i); int andIncrement = atomicInteger1.getAndIncrement(); System.out.println( "先返回再增加" +andIncrement); int i1 = atomicInteger1.addAndGet( 10 ); System.out.println( "先增加在返回" +i1); int andSet = atomicInteger1.getAndSet( 866 ); System.out.println( "得到旧值返回新值" +andSet); } |
创建对象,通过方法操作数据
1 2 3 4 5 6 7 8 9 10 11 12 13 | private int count= 0 ; @Override public void run() { for ( int i = 0 ; i < 100 ; i++) { //count++不是原子操作 //1 count获取共享数据的值,到栈变量副本中 //2将本线程栈变量副本的值+1 //3 将变量副本的值更新到共享数据 //4其中任何一部都可能丢失cup的执行权 count++; System.out.println( "已经送了" +count+ "个鸡蛋" ); } } |
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class MyRunnable implements Runnable{ private AtomicInteger atomicInteger= new AtomicInteger( 0 ); @Override public void run() { for ( int i = 0 ; i < 100 ; i++) { //count++不是原子操作 //1 count获取共享数据的值,到栈变量副本中 //2将本线程栈变量副本的值+1 //3 将变量副本的值更新到共享数据 //4其中任何一部都可能丢失cup的执行权 int count=atomicInteger.incrementAndGet(); System.out.println( "已经送了" +count+ "个鸡蛋" ); } } } |
1 2 3 4 5 6 7 8 | public class MyTest { public static void main(String[] args) { MyRunnable myRunnable= new MyRunnable(); for ( int i = 0 ; i < 100 ; i++) { new Thread(myRunnable).start(); } } } |
AtomicInteger原理 自旋锁 +CAS算法
CAS算法
增加了保存旧值的内存,修改时若旧值,和内存值(共享数据)比较,一样没被修改过,写入,不一样,修改失败。再重新获取最新值。 (这个重新获取)重新获取过程叫做自旋
例如 AtomIcIntager
乐观锁:持有一个乐观的态度,不会通过(锁机制)上锁,操作过程通过判断,来判断是否有其他线程影响了数据,影响了数据,则终止本次操作,在重新获取数据进行操作
悲观锁:认为在本线程操作过程中,会有其他线程来影响,直接加锁(同步代码块,同步方法,Lock)
并发工具类
HashMap是线程不安全的(多线程下不安全)为保证数据的安全性我们使用hashtable(Map的实现类)里面的方法采用同步方法,但是效率低下(采用有线程调用时悲观锁直接锁起整张表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class MyHashMap { public static void main(String[] args) { HashMap<String,String> hashMap= new HashMap<>(); Thread t1= new Thread(()->{ for ( int i = 0 ; i < 25 ; i++) { hashMap.put(i+ "" ,i+ "" ); ; } }); Thread t2= new Thread(()->{ for ( int i = 25 ; i < 50 ; i++) { hashMap.put(i+ "" ,i+ "" ); } }); t1.start(); t2.start(); for ( int i = 0 ; i < 51 ; i++) { System.out.println(hashMap.get(i + "" )); } } } |
Hashtable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MyHashMap { public static void main(String[] args) throws InterruptedException { Hashtable<String,String> hashMap= new Hashtable<>(); Thread t1= new Thread(()->{ for ( int i = 0 ; i < 25 ; i++) { hashMap.put(i+ "" ,i+ "" ); ; } }); Thread t2= new Thread(()->{ for ( int i = 25 ; i < 51 ; i++) { hashMap.put(i+ "" ,i+ "" ); } }); t1.start(); t2.start(); Thread.sleep( 1000 ); for ( int i = 0 ; i < 51 ; i++) { System.out.println(hashMap.get(i + "" )); } } } |
效率低是因为锁的颗粒度比较大(锁住整张hash表)
ConcurrentHashMap 安全,且兼顾效率 Map的实现类
Segment[] 存放地址值,该地址值是第一次计算的哈希值相同数据组成的哈希表的地址
JDK1.8做了优化 底层;哈希表 (数组,红黑树,链表)
安全机制;CAS +synchronized同步代码块
大数组可以扩容 l en*0.75 扩容
链表数据到达8,自动转为红黑树结构
多线程场景下,双列集合。优先使用ConcurrentHashMap 单列集合 Colletions (api中写的很清楚) 例如 Collections.SynchronizedList(实现类对象)返回一个线程安全的集合
(倒计数的门)
CountDownLatch 让某一条线程等待其他先后曾执行完毕后调用
妈妈等着五个孩子吃完,检查作业 6个线程 等待线程5个
countdown 倒计时
构造方法
public CountDownLatch(需要等待线程的数量)
countDownLatch.awite()
countDownLatch.countDown()
Semaphore 信号 管理员 管理同时执行多少条线程(一个路口可以通过几辆车,有通行证的车可以通过)
没有空参构造,参数允许几条线程运行(几张通行证)
semaphore.acquire(获得)(获得通行证)
semaphore.realse(释放) 释放通行证