1基本概念
进程
通常指在计算机中运行的应用程序(进程是资源(CPU、内存等)分配的基本单位)
(通过任务管理器可看到详细信息,每个进程有他的id,运行状态,不同进程会分配单独的内存空间,数据改变和运算会消耗cpu资源。故,应用程序的使用要占内存和CPU)
每个应用程序 有自己独立的内存空间
(程序运行时系统就会创建一个 进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。)
线程
每个应用程序 调用cpu时的最小调度单位(读写通道)
(几核几线程,CPU有几个核就有几个处理单元;有几个线程就有几个通道通向CPU来跟内存进行数据交互。)
(应用程序运行时,在内存中开辟的空间,与CPU进行数据交互时的通道即线程。)
(进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。)
在多核多线程cpu的计算机中 允许程序 开启多线程处理任务 可以节省时间
进程与线程区别:
1.进程是资源分配的最小单位,线程是程序执行的最小单位。
2.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3.线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据。进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4.但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了, 而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
5.一个进程有多个线程。
并发
多个事物同时发生
串行
顺序处理
并行
同时处理
同步
按照顺序处理
(多个事物不能同时进行,必须一个一个的来,上一个事物结束后,下一个事物才开始.(程序里一般叫做同步方式处理)
(串行处理方式 )
异步
同时处理
(多个事物同时进行,不需要等待另一个事件的执行.(程序里一般叫做异步方式处理))
阻塞
不通状态
(描述执行过程的状态,阻塞意味着停下来等待 wait)
(阻塞伴随同步出现)
非阻塞
通行状态
组合使用:
1.所谓同步/异步,关注的是能不能同时开工。
2.所谓阻塞/非阻塞,关注的是能不能动。
同步阻塞,不能同时开工,也不能动。只有一条小道,一次只能过一辆车,可悲的是还都堵上了。
同步非阻塞,不能同时开工,但可以动。只有一条小道,一次只能过一辆车,幸运的是可以正常通行。
异步阻塞,可以同时开工,但不可以动。有多条路,每条路都可以跑车,可气的是全都都的堵上了。
异步非阻塞,可以同时开工,也可以动。有多条路,每条路都可以跑车,很爽的是全都可以正常通行。
3.回到程序里,把它们和线程关联起来:
同步阻塞,相当于一个线程在等待。
同步非阻塞,相当于一个线程在正常运行。
异步阻塞,相当于多个线程都在等待。
异步非阻塞,相当于多个线程都在正常运行。
2线程的创建方式 常用方法
Thread类(线程核心的类)
比较常用的构造方法:
1.Thread(Runnable target) 传入Runnable接口
2.Thread(String name) 传入字符串(线程名)
3.Thread()
4.Thread(Runnable target,String name)
(1,4是一套;2,3,是一套)
创建线程有几种不同的方式:
1.让类继承Thread类,里面会重写一个run()方法。若想开多线程处理,处理的代码写在run()方法里。
2.方式二:实现Runnable接口,该接口只定义了一个run()抽象方法,故重写run()方法
3.方式三:实现Callable接口,且重写call()方法,该方法需要返回值
4.方式四:使用线程池(jdk提供的线程池)
几个重要的方法:
1.start() 启动线程 (之后java虚拟机会自动调用run()方法执行,别自己调,自己调就是普通方法,就走一次)
2.join() 让指定的线程优先执行结束
3.interrupted() 主动中断某线程
4.sleep() 指定线程休眠一段时间
5.yield() 把当前线程转回就绪状态 与其他线程重新抢资源(cpu)
6.run(){ 需要重写 并且把线程需要执行的代码写入run方法 }
7.setPriority()设置优先级 1-10
8.wait() 挂起(等待)线程
9.notify() 唤醒线程
10.notifyAll() 唤醒所有挂起(等待)的线程
单线程:
public static void main(String[] args) { //main() 程序的入口。程序执行都有线程执行,main()是程序的主线程,主线程是一个线程。故平时不开启多线程,程序是单线程。 // 其实垃圾回收gc线程也在跟着主线程执行。(创建字符串对象,在内存中占空间,程序运行结束,gc将空间清空。传统的编程语言 // 比如c语言,需要自己找到内存地址,然后自己去清空) //gc在后台运行,一般叫守护线程(没法主动操作,由java虚拟机自动调配。伴随着主线程的结束而结束 ) System.out.println("abc"); //查看当前运行的线程的名字 System.out.println(Thread.currentThread().getName()); //main //Thread[线程名称,优先级] //优先级高的CPU优先执行,可在任务管理器中对进程点右键,设置优先级。java中有1-10个优先级(由低到高),主线程默认5 System.out.println(Thread.currentThread()); //Thread[main,5,main] //设置线程优先级(在多线程运行时才会看出效果) Thread.currentThread().setPriority(10); try { //线程休眠,参数是毫秒 Thread.sleep(200); } catch (InterruptedException e) { //线程中断异常 e.printStackTrace(); } System.out.println("over"); }
在主线程里将卖票这个事扔给子线程处理:
创建子线程:
public class MyThread extends Thread{ //创建一个新的类,要让这个类和线程有关: //方式一:继承Thread类,且必须重写Thread类里的run()方法,在run方法里写要处理的事情。 private static Integer ticket = 10; //默认10张票 @Override public void run() { //让子线程执行的代码 while (ticket>0){ ticket--; System.out.println("卖了一张票,还剩"+ticket); try { Thread.sleep(200); //每卖一张票,休息一会 } catch (InterruptedException e) { e.printStackTrace(); } } } }
主线程与子线程建立关系:
public class Demo1 {
public static void main(String[] args) {
//让主线程和子线程建立关系,创建子线程
Thread newThread = new MyThread();
//启动子线程
newThread.start();
System.out.println("执行完了"); //执行完该代码,才执行子线程
//CPU处理数据,若多线程,由CPU自己调度,从线程里拿数据。多个线程同时过来,按优先级选。若优先级相同,一般会执行代码较少的一方。
//多线程同时执行,抢CPU资源,CPU理论上按优先级处理(会偏向于优先级高的执行)。
}
}
如何让子线程先执行完,再执行主线程:
public class Demo1 {
public static void main(String[] args) {
Thread newThread = new MyThread();
newThread.start();
try {
//子线程执行结束,再执行主线程
//newThread.join(); 指定newThread优先执行,必须该线程执行结束,其它线程才能执行
newThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完了"); //控制台最后打印执行完了
}
}
//休眠语句的其它写法: java.util.concurrent.TimeUnit
//TimeUnit可先选时间单位
try {
TimeUnit.MILLISECONDS.sleep(300); //等同于Thread.sleep(300); 推荐TimeUnit的写法
} catch (InterruptedException e) {
e.printStackTrace();
}
创建子线程的第二种方式:
public class MyThread implements Runnable{
//创建一个新的类,要让这个类和线程有关:
//方式二:实现Runnable接口,该接口只定义了一个run()抽象方法,故重写run()方法
private static Integer ticket = 10; //默认10张票
@Override
public void run() {
//让子线程执行的代码
while (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
try {
Thread.sleep(200); //每卖一张票,休息一会
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
对应的主线程:
public class Demo2 { public static void main(String[] args) { //使用Thread(Runnnable target) 传入其实现类 //Thread newThread = new Thread(new MyThread()); //也可同时给线程起名 Thread newThread = new Thread(new MyThread(),"窗口1"); newThread.start(); try { newThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完了"); } }
当然第一套也可加名称:
public class Demo1 {
public static void main(String[] args) {
//Thread newThread = new MyThread();
Thread newThread = new MyThread("窗口1"); //注意子线程里要有对应的有参构造
newThread.start();
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完了");
}
}
public class MyThread extends Thread{ private static Integer ticket = 10; @Override public void run() { //让子线程执行的代码 while (ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } public MyThread(String name) { super(name); } }
对于Thread newThread = new Thread(new MyThread(),"窗口1");也可用匿名内部类,对应的子线程也不用实现Runnnable类,不用重写run()方法,写普通方法即可:
public class MyThread { private static Integer ticket = 10; public static void sale() { while (ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Demo2 {
public static void main(String[] args) {
Thread newThread = new Thread(new Runnable(){
@Override
public void run(){
MyThread.sale();
}
},"窗口1");
newThread.start();
try {
newThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完了");
}
}
此写法也有简化写法:(可以简化成λ表达式)
Thread newThread = new Thread(()->{
MyThread.sale();
},"窗口1");
第三种创建线程的方式:
public class MyThread implements Callable { //方式三:实现Callable接口,且重写call()方法,该方法需要返回值 private static Integer ticket = 10; @Override public Object call() throws Exception { //让子线程执行的代码 while (ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } return ticket; } }
public class Demo3 { public static void main(String[] args) { //创建FutureTask要传入Callable<V>事件或Runnable事件。 //FutureTask(Callable<V>) FutureTask(Runnable,V) //用FutureTask(Callable<V>)创建: MyThread myThread = new MyThread(); FutureTask task = new FutureTask(myThread); //FutureTask异步任务 Thread newThread = new Thread(task); //创建出的异步任务传给线程对象 newThread.start(); //task.get(); 可拿到异步任务的结果。(子线程重写call方法有返回值,认为异步任务调用结束可能需要知道结果) //不加get方法,先执行主线程再执行子线程,即先输出"执行完了";加入get方法,先执行子线程。再执行主线程,即最后输出"执行完了"。 //task.get();要求先打印异步任务结果 //当然,若该代码放在System.out.println("执行完了");之后,则先主线程执行。 try { System.out.println(task.get()); //0 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("执行完了"); } }
方式三现场实现的方法:
public class MyThread{ private static Integer ticket = 10; public static void sale() throws Exception { //让子线程执行的代码 while (ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } public static Integer getTicket() { return ticket; } }
public class Demo3 {
public static void main(String[] args) {
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
MyThread.sale();
return MyThread.getTicket();
}
});
Thread newThread = new Thread(task);
newThread.start();
try {
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("执行完了");
}
}
也可用lamda表达式 :
FutureTask task = new FutureTask(()-> {
MyThread.sale();
return MyThread.getTicket();
});
第四种创建线程的方式:
资源复用:将东西分离,看哪些可共用,可共用的用完之后不让它马上回收,建一个集合将它们装起来。一般,该集合称为各种资源池。java里提供了线程池,装的线程对象,用法:先创建线程池,将要执行的代码提交给获取的线程,让其执行。执行结束,对象回归到线程池。(类似数据库连接池)
线程池核心类:ThreadPoolExecutor,即线程池生成器。
public static void main(String[] args) { //官方提供的Executors,在java.util.concurrent包。第三方也有,这里说官方的。 ExecutorService executorService = Executors.newCachedThreadPool(); //缓冲线程池,自己扩容,自己控制大小 ExecutorService executorService1 = Executors.newFixedThreadPool(10); //指定初始大小的线程池 //可延时调度线程池,也指定初始大小 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10); ExecutorService executorService2 = Executors.newSingleThreadExecutor(); //单线程线程池(测试用) }
public class Demo4 { public static void main(String[] args) { //这些线程池用法类似,以指定初始大小的线程池为例: //注:创建线程池的方法很多,但底层大多在用ThreadPoolExecutor去创建线程池。 ExecutorService executorService1 = Executors.newFixedThreadPool(10); //executorService1.submit(Callable<T> task) //executorService1.submit(Runnable task) executorService1.submit(()->{ //要执行的代码提交给线程池 try { MyThread.sale(); } catch (Exception e) { e.printStackTrace(); } }); //一旦线程池创建,就有专门维护线程状态的线程存在,需要主动关闭线程池 executorService1.shutdown(); } }
public class MyThread { private static Integer ticket = 10; public static void sale() throws Exception { //让子线程执行的代码 while (ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } public static Integer getTicket() { return ticket; } }
自己手动创建线程池,不用其他人提供的:
先看ThreadPoolExecutor的相关参数:(面试题或笔试题)
public ThreadPoolExecutor(int corePoolSize, 启动时创建的线程个数 int maximumPoolSize, 最大线程个数 long keepAliveTime, 存活时间(超过存活时间,自动释放,不浪费线程池空间) TimeUnit unit, 时间单位(与存活时间搭配使用) BlockingQueue<Runnable> workQueue, 任务队列(要执行代码会先放到任务队列,之后由线程池自动分配给线程。任务队列有几种不同的实现策略,如下: //ArrayBlockingQueue 有界队列(数组方式) 可以指定长度(参数部分) //LinkedBlockingQueue 无界队列(链表方式,一个挨一个,谁先进谁执行) //PriorityBlockingQueue 优先队列 默认按类名排(可自定义排序规则) //SynchronousQueue 同步队列(一旦有任务进来必须马上交给线程处理) ) ThreadFactory threadFactory, 创建线程对象的默认工厂函数(一般选用默认工厂创建方式,Executors.defaultThreadFactory()) RejectedExecutionHandler handler) { 拒绝策略(比如:选用有界队列,线程在执行,任务队列也放满了,再有任务进来,就得指定处理方式。有如下几种方式: AbortPolicy 直接放弃 CallerRunsPolicy() 调用主线程,让主线程处理 ) threadPoolExecutor.submit(MyThread::sale); 把执行的代码提交给线程池执行 shutdown() 关闭线程池
public class Demo4 {
public static void main(String[] args) {
//concurrent并发,和并发或多线程相关的都在java.util.concurrent包里
//手动创建线程
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5,10,60l, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
//使用方式和调用官方提供线程的使用方式一样
threadPoolExecutor.submit(()->{
try {
MyThread.sale();
} catch (Exception e) {
e.printStackTrace();
}
});
threadPoolExecutor.shutdown();
}
}
创建方式很多,先写出一种,熟悉以后,再替换成其它方式。使用方式和结果都是差不多的。
3.多线程并发问题
数据安全性
多线程 对共享数据进行写操作 //MESI缓存一致性协议 //(加载到缓存里的数据会打成标记,由专门的监控去监控这些数据。打上的标记主要有:独享数据,共享数据,改变状态,作废状态。若只有一个核处理数据,该数据会被标记成独享数据;若多个核写数据,标记成共享数据;若共享数据,一个核使数据发生改变(新的数据称为A),其它核会将数据标记为作废状态,直接抛弃,再次从一级,二级,三级缓存依次找该数据。此时A已到达三级缓存,其它核会处理A数据,而不是原先的数据,jvm的jmm就是模拟的这个过程) //多线程间写操作 会有线程安全问题 //jvm 模拟计算机运行环境(故称为jvm虚拟机) //jmm 内存管理模型 //(CPU有一级,二级,三级缓存,三级缓存允许多核间共享。数据来源在内存。处理数据时:先读,内存加载数据,传到缓存3。CPU调用数据,先从一级缓存找,找不到去二级找,还没有去三级缓存找。然后写,依次经过一二三,回到内存。这是单线程处理数据流程。 //但到了多线程,多个处理单元时,写操作时,经过缓存一二,都到达缓存三,就可能出现问题。) // monitor 监视器 //(代码中无法直接写monitor,但一些代码可直接触发monitor,如下几个: //1.悲观锁:1.synchronized 2.lock //1.1 synchronized 同步 触发监视器 //(将需要监控的代码用synchronized包起来,保证每次只有一个线程执行。) //1.2 lock //2.乐观锁 //2.1 volatile+compareAndSwap 保证线程安全 //compareAndSwapLong CAS乐观锁的具体实现 //1.可见性 2.有序性 3.原子性 )
在2中是开启一个线程,看下开启多个线程的情况:
public class Demo5 { public static void main(String[] args) { //开三个线程,并都启用。希望运行时间变为原来的三分之一 //但多线程竞争资源会有数据不安全情况出现。ticket用static修饰,属于类变量,可在不同代码间共享。但计算机执行线程是随意挑的。 //若没有ticket--;只是一堆线程读这个数,不会有问题。 Thread newThread1 = new Thread(()->{ MyThread.sale(); },"窗口1"); Thread newThread2 = new Thread(()->{ MyThread.sale(); },"窗口2"); Thread newThread3 = new Thread(()->{ MyThread.sale(); },"窗口3"); newThread1.start(); newThread2.start(); newThread3.start(); System.out.println("执行完了"); } }
3.1同步代码块
synchronized (监视器对象){//使用同一个对象 "xxx"intern() 从地址返回同一个对象
//需要顺序执行的代码
}
用synchronized 触发监控,解决多线程问题。(synchronized同步,做同步代码块(程序执行时,特定代码段必须顺序执行,这种做法也叫悲观锁,多个人争抢资源,认为一定会出错,让一个一个来,但效率很低)。)
view--show ByteCote,虚拟机执行的机器码,找MONITOR,可看到MONITORENTER(监控进入),监控执行代码,MONITOREXIT监控离开。
public class MyThread {
private static Integer ticket = 10;
public static void sale(){
//让子线程执行的代码
while (ticket>0){
//synchronized的参数"mylock".intern(),可保证使用的监视器是同一个对象对代码进行监控。
//保证了线程一个一个来
synchronized ("mylock".intern()){
//加if,满足限制,才让线程执行。
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static Integer getTicket() {
return ticket;
}
}
3.2同步方法
执行此方法的线程 需要排队执行 public synchronized static void diffTicket(){ if(ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票 还剩"+ticket); } } 注意:1.分析此方法是否可以直接加同步synchronized 2.同步方法 默认使用当前类型做监视器对象 类型锁 MyThread.class
//在方法上加synchronized,只要执行这个方法的线程都要排队。比如线程1,执行该方法,执行循环,将票卖完。然后其他线程再来执行该方法 //希望几个线程一起卖票,不是单独一个线程卖完 public synchronized static void sale(){ while (ticket>0){ if(ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); } } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } }
将需要同步的部分,单独拿出来写一个方法:
public class MyThread { private static Integer ticket = 10; //希望几个线程一起卖票,不是单独一个线程卖完 public synchronized static void sale(){ while (ticket>0){ diffTicket(); } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized static void diffTicket(){ if(ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket); } } }
之前说,使用synchronized,要有对象充当监视器,同步方法的监视器是谁?(类型对象。类一般指实例对象,类型对象两个。实例对象一般通过new创建,类型对象是虚拟机创建。方法上加synchronized,默认使用类型对象。即同步方法默认使用类型锁)同步方法等同于如下代码:
public class MyThread { private static Integer ticket = 10; public synchronized static void sale() { while (ticket > 0) { synchronized (MyThread.class) { if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket); } } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } }
3.3LOCK
使用lock 使代码顺序执行 private static final Lock mylock = new ReentrantLock(); mylock.lock(); 上锁 mylock.unlock(); 解锁 lock遇到异常不会自动释放锁对象 synchronized 遇到异常 会自动释放锁对象 try{ if(ticket>0){ ticket--; System.out.println(Thread.currentThread().getName()+"卖了一张票 还剩"+ticket); if("窗口1".equals(Thread.currentThread().getName())){ throw new RuntimeException("窗口被打劫了 卖不了了...."); } } }finally { mylock.unlock(); }
public class MyThread { private static Integer ticket = 10; //创建锁对象 java.util.concurrent.locks.Lock; lock也是悲观锁,进来都得排队 //Lock里有很多实现类 private static final Lock myLock = new ReentrantLock(); public synchronized static void sale() { while (ticket > 0) { //需要顺序执行的地方加myLock.lock(); myLock.lock(); if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket); } //结束的地方加myLock.unlock(); myLock.unlock(); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } }
使用锁,若碰到异常,不会自动释放。
myLock.lock(); if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket); throw new RuntimeException("窗口被打劫,卖不了了..."); } //执行过程出现异常,将卡死,因为myLock.unlock();无法执行,没有解锁。 myLock.unlock();
如何处理:(加try...finally...结构)
myLock.lock(); try{ if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket); throw new RuntimeException("窗口被打劫,卖不了了..."); } }finally { myLock.unlock(); }
3.4 volatile+CAS
volatile 修饰变量 //可见性 有序性 CAS 乐观锁 //compareAndSwapLong CAS乐观锁的具体实现 AtomicLong LongAdder 如果多线程 需要使用到数字的加减 一般使用官方提供的操作方式 Unsafe直接操作内存地址,不建议直接使用。(底层自己用,给你写好功能) 地址在内存如何存?由基础地址+偏移量(offset)决定。 valueoffset 数据在内存中真正的位置 volatile 修饰变量,可保证数据在线程间的可见性 数据想安全,有3个点要保证: 1.可见性 2.有序性 3.原子性 悲观锁可保证三个特性。但volatile只能保证前两点,不能保证操作的原子性。 但volatile+compareAndSwap 保证线程安全 compareAndSwap对比并且替换,CAS乐观锁的具体实现。 乐观锁思想:预期的和出现的一致时,才会执行代码(所有的都能过来,不是自己想要的,不做处理) 悲观锁思想:一个一个来(全部堵死,就留一个通道) 乐观锁换到数据上:就是在内存地址里数据可自由改变,但一直做对比,看数据与预期值是否一样,一样时才运行。 乐观锁的代码很难写,故官方底层写好,提供了一些方式。
模拟1000个线程拿id:
public class IdGenerator { private static Long id = 0l; public static void myIncrement(){ id++; } public static Long getId() { return id; } } class Test{ public static void main(String[] args) { //模拟1000个线程同时拿id的情况: List<Thread> listThread = new ArrayList<Thread>(); for (int i = 0; i < 1000; i++) { listThread.add(new Thread(()->{ IdGenerator.myIncrement(); })); } //启动线程 listThread.forEach(Thread::start); //子线程走完,再走主线程 for(Thread thr:listThread){ try { thr.join(); } catch (InterruptedException e) { e.printStackTrace(); } } //读取结果 System.out.println(IdGenerator.getId()); } }
以上未做限制,处理方式若用悲观锁
synchronized ("lick".intern()){ id++; }
效率偏低,就相当于一个线程id自增操作。
用乐观锁:
public class IdGenerator { //private static Long id = 0l; //Atomic原子,使用官方提供的原子操作类(乐观锁) private static AtomicLong id = new AtomicLong(0); //参数为初始值 public static void myIncrement(){ // id++; //需要自增时的操作 id.incrementAndGet(); } public static Long getId() { //需要获取值时 return id.get(); } }
官方提供的另外一种:
public class IdGenerator { //官方提供的LongAdder private static LongAdder id = new LongAdder(); //给LongAdder添值 static { id.add(100l); //初始值从1000开始 } public static void myIncrement(){ //需要自增时的操作 id.increment(); } public static Long getId() { //需要获取值时 return id.longValue(); } }
面试:CAS思想是什么?
4单例
单例在单线程里的使用:
public class SingletonDemo { private SingletonDemo(){} private static SingletonDemo mysig = new SingletonDemo(); public static SingletonDemo getInstance(){ return mysig; } } /*class SingletonDemo{ private SingletonDemo(){} private static SingletonDemo mysig; public static SingletonDemo getInstance(){ if(mysig == null){ mysig = new SingletonDemo(); } return mysig; } }*/ class Test{ public static void main(String[] args) { System.out.println(SingletonDemo.getInstance()); } }
懒汉模式在多线程里,不能保证是同一个对象:
public class SingletonDemo{ private SingletonDemo(){} private static SingletonDemo mysig; public static SingletonDemo getInstance(){ if(mysig == null){ mysig = new SingletonDemo(); } return mysig; } } class Test{ public static void main(String[] args) { //多线程里获取对象,结果是有些数据不一样 //同一时间很多线程挤进去,new SingletonDemo();不止执行一次,不在同一时间挤进去的线程,获取对象是同一个。 //即刚创建时,一堆线程挤进来,创建了好几个对象。 new Thread(()->{ System.out.println(SingletonDemo.getInstance()); }).start(); new Thread(()->{ System.out.println(SingletonDemo.getInstance()); }).start(); new Thread(()->{ System.out.println(SingletonDemo.getInstance()); }).start(); } }
为保证是同一个对象,要加同步:
public static SingletonDemo getInstance(){ synchronized (SingletonDemo.class) { if (mysig == null) { mysig = new SingletonDemo(); } } return mysig; }
加同步保证了最初挤进来的线程,一个个排列。但后续的不需要排列,也被强行排列。故进一步使用双重检测的方式:(典型的面向过程的方式)
public static SingletonDemo getInstance(){ if (mysig == null) { synchronized (SingletonDemo.class) { if (mysig == null) { mysig = new SingletonDemo(); } } } return mysig; }
前人总结,即使这样写还会出现概率极低的错误:(java虚拟机里有及时编译机制,叫JIT,用于提高虚拟机执行效率。一堆对象创建好,有的关联这批,有的关联那批,JIT执行时,哪种效率最高,使用哪种方式,故使用时真正执行效率和想象的顺序可能不一样,但结果一样。单线程没问题,但多线程中,顺序不一样,可能导致数据乱串。)故,还要有一步:
//加volatile关键字,保证可见性和有序性(可以阻止代码重排。代码重排是JIT为了提高虚拟机效率的一种策略) private volatile static SingletonDemo mysig; public static SingletonDemo getInstance(){ if (mysig == null) { synchronized (SingletonDemo.class) { if (mysig == null) { mysig = new SingletonDemo(); } } } return mysig; }
笔试题:写多线程里的单例模式。
单例在多线程使用时 public class SingletonDemo { /* * 饿汉 * * 懒汉 延迟加载 * * */ //private static SingletonDemo mysig = new SingletonDemo(); private volatile static SingletonDemo mysig; public static SingletonDemo getInstance(){ //JIT 即时编译 代码重排 提高虚拟机执行效率 //volatile 可以阻止代码重排 //双重检测 保证安全性 和后续使用的效率 if(mysig==null){ //排队创建 synchronized (SingletonDemo.class){ //延迟加载 if(mysig==null){ mysig = new SingletonDemo(); } } } return mysig; } //私有构造 private SingletonDemo(){} }
单例模式的另一个笔试题,与单例冲突的情形一(也叫打破单例)(平时不会这样写):(和线程没关系)
public class SingletonDemo implements Cloneable{ //类可实现克隆接口 private SingletonDemo(){} private volatile static SingletonDemo mysig; public static SingletonDemo getInstance(){ if (mysig == null) { synchronized (SingletonDemo.class) { if (mysig == null) { mysig = new SingletonDemo(); } } } return mysig; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Test{ public static void main(String[] args) { SingletonDemo instance = SingletonDemo.getInstance(); //创建对象 Object clone = null; try { clone = instance.clone(); //克隆对象 } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(instance); System.out.println(clone); //结果:不是同一个实例对象 } }
如何保证是同一个实例对象:(在重写的克隆方法里,将返回值改成同一个对象)
@Override protected Object clone() throws CloneNotSupportedException { return mysig; }
与单例冲突的情形二:反序列化
解决方式:readResolve() 返回对象的方法。允许重写,和克隆重写一样,返回同一个对象即可。
5死锁
多线程间 互相持有了对方需要的资源 并且不释放
避免死锁 如果有嵌套使用 认真检查充当监视器的对象
public class DeadLock { //画家画画,需要画笔和颜料两个资源,但资源同一时间只能给一个人用 private static String lock1 = "画笔"; private static String lock2 = "颜料"; public static void wangPrint(){ synchronized (lock1.intern()){ System.out.println("小王拿到了画笔"); synchronized (lock2.intern()){ System.out.println("小王拿到了颜料"); System.out.println("小王画画"); } } } public static void liPrint(){ synchronized (lock2.intern()){ System.out.println("小李拿到了颜料"); synchronized (lock1.intern()){ System.out.println("小李拿到了画笔"); System.out.println("小李画画"); } } } } class Test{ public static void main(String[] args) { //通过两个线程,让它们同时画画 new Thread(()->{ DeadLock.wangPrint(); }).start(); new Thread(()->{ DeadLock.liPrint(); }).start(); //结果是:小王拿到了画笔,小李拿到了颜料。都在等对方释放资源,程序无法停止 } }
6线程通信
上面讲了多个线程做一个事会产生的问题。还有线程间协作的问题,也叫线程通信问题。(当前线程控制其他线程的运行和挂起)
多线程间 存在 使用不同的代码 但是操作的是相同的资源 需要线程暂停 和线程继续执行 synchronized OrdeDemo.class.wait() 当前持有监视器的线程等待 OrdeDemo.class.notify() 由于当前监视器对象等待的线程唤醒 private static final Lock mylock = new ReentrantLock(); private static Condition mycond = mylock.newCondition(); mycond.await(); 等待 mycond.signal(); 唤醒
线程通信经典模型:生产者-消费者模型
public class OrdeDemo { //生成订单,处理订单,还有个集合维护订单状态 //多线程下对集合写操作,要用安全的集合。 //或用Collections.synchronizedList()拿到安全的集合 private static List<String> orderList = Collections.synchronizedList(new ArrayList<>()); //维护订单状态 //生成订单 public static void produceOrder(){ orderList.add("新订单"); //对象用字符串模拟一下 System.out.println("生成了新订单 当前订单数:" + orderList.size()); } //处理订单 public static void handleOrder(){ orderList.remove(0); //用移除对象作为处理订单结束的标志,参数是索引 System.out.println("处理了订单 当前订单数:" + orderList.size()); } } class Test{ public static void main(String[] args) { //让线程启动后不停生产订单 new Thread(()->{ while(true){ OrdeDemo.produceOrder(); try { TimeUnit.MILLISECONDS.sleep(400); //每生产一个休息400毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); //消费订单,当集合里没东西时,应挂起 new Thread(()->{ while(true){ OrdeDemo.handleOrder(); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
当集合里没东西时,应挂起:
public static void handleOrder(){ //监视器应配合同步去用 synchronized (OrdeDemo.class){ if(orderList.size() == 0){ System.out.println("当前没有订单"); try { //让线程暂停,wait()属于Object对象。 //new Object().wait(); OrdeDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } orderList.remove(0); //用移除对象作为处理订单结束的标志,参数是索引 System.out.println("处理了订单 当前订单数:" + orderList.size()); }
当线程生产订单,集合里不空时,另一个线程应该消费订单,但并没有。故,生产订单的线程应唤醒它:
public static void produceOrder(){
synchronized (OrdeDemo.class){
orderList.add("新订单"); //对象用字符串模拟一下
System.out.println("生成了新订单 当前订单数:" + orderList.size());
//同样先找到监视器。notify()唤醒一个 notifyAll()都唤醒
//同样,监视器对象直接拿出来用不行,仍要配合同步
OrdeDemo.class.notify();
}
}
若再加一个生产订单的线程,但不想让订单无限制的增长,要求集合里订单达到10个,生产的线程休息:
public static void produceOrder(){ synchronized (OrdeDemo.class){ if(orderList.size() == 10){ System.out.println("当前订单太多 歇一会"); try { OrdeDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } orderList.add("新订单"); System.out.println("生成了新订单 当前订单数:" + orderList.size()); OrdeDemo.class.notify(); } }
同样也要有对应的唤醒:
public static void handleOrder(){ synchronized (OrdeDemo.class){ if(orderList.size() == 0){ System.out.println("当前没有订单"); try { OrdeDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } orderList.remove(0); System.out.println("处理了订单 当前订单数:" + orderList.size()); OrdeDemo.class.notify(); }
但还是有问题,wait()方法应按官方给的案例进行:(条件的部分用while )
public static void produceOrder(){ synchronized (OrdeDemo.class){ while (orderList.size() == 10){ System.out.println("当前订单太多 歇一会"); try { OrdeDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } orderList.add("新订单"); System.out.println("生成了新订单 当前订单数:" + orderList.size()); OrdeDemo.class.notify(); } } //处理订单 public static void handleOrder(){ synchronized (OrdeDemo.class){ while(orderList.size() == 0){ System.out.println("当前没有订单"); try { OrdeDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } orderList.remove(0); System.out.println("处理了订单 当前订单数:" + orderList.size()); OrdeDemo.class.notify(); }
以上是用synchronized处理生产者-消费者问题,下面用其它方式:
测试类同上,其它代码如下:
public class OrdeDemo2 {
private static List<String> orderList = Collections.synchronizedList(new ArrayList<>());
//创建锁对象
private static final Lock myLock = new ReentrantLock();
private static Condition mycond = myLock.newCondition(); //通过锁对象拿到Condition对象
//Condition对象可用来替代监视器对象,对线程挂起和唤醒
public static void produceOrder(){
// synchronized (OrdeDemo.class){
myLock.lock();
try {
while (orderList.size() == 10){
System.out.println("当前订单太多 歇一会");
try {
//OrdeDemo.class.wait();
//wait() await() 都可传秒数,指定一定时间唤醒,若不传时间,则一直等待。
mycond.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
orderList.add("新订单");
System.out.println("生成了新订单 当前订单数:" + orderList.size());
// OrdeDemo.class.notify();
//signal() signalAll() 也有两个
mycond.signal();
}finally {
myLock.unlock();
}
// }
}
public static void handleOrder(){
// synchronized (OrdeDemo.class){
myLock.lock();
try {
while(orderList.size() == 0){
System.out.println("当前没有订单");
try {
//OrdeDemo.class.wait();
mycond.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// }
orderList.remove(0);
System.out.println("处理了订单 当前订单数:" + orderList.size());
// OrdeDemo.class.notify();
mycond.signal();
}finally {
myLock.unlock();
}
}
}
7线程状态
public class ThreadStatus { public static void main(String[] args) { Thread mythread = new Thread(() -> { System.out.println("aaa"); }); //查看线程状态 System.out.println(mythread.getState().name()); //new 新创建线程,其状态就是new } }
public class ThreadStatus {
public static void main(String[] args) {
Thread mythread = new Thread(() -> {
System.out.println("aaa");
System.out.println(Thread.currentThread().getState().name()); //runnable
//线程执行过程中的状态:runnable 执行状态
});
mythread.start();
}
}
public class ThreadStatus {
public static void main(String[] args) {
Thread mythread = new Thread(() -> {
System.out.println("aaa");
System.out.println(Thread.currentThread().getState().name());
});
mythread.start();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(mythread.getState().name()); //terminated 终极状态
}
}
线程状态都在status里,里面有枚举类,如下:
/** * Thread state for a thread which has not yet started. */ // NEW, 初始状态 (新建线程时的状态) /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ // RUNNABLE, 运行状态 /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ // BLOCKED, 阻塞状态 /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ // WAITING, 等待状态 /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ //TIMED_WAITING, 等待指定时间 (有限时的等待) /** * Thread state for a terminated thread. * The thread has completed execution. */ //TERMINATED; 线程结束笔试面试题:1.哪些方法会触发哪些状态
2.线程转化状态
3.wait 和sleep的区别
sleep由Thread对象调用,会进入的状态是等待状态
wait由监视器对象调用,不唤醒就是阻塞状态,唤醒就是等待状态。
wait等待时其它线程会执行,sleep不会主动释放锁资源让其它线程执行,wait会主动释放锁资源让其它线程执行。
(sleep 不释放锁 调用对象不同 线程状态不同
wait 释放锁)