外贸网站seo推广,wordpress 账号 有效期,网站建设必学课程,做心悦腾龙光环的网站目录
创建线程
方法一#xff1a;继承Thread类来创建一个线程类
方法二#xff1a;实现Runnable#xff0c;重写run
线程等待
获取当前线程引用
休眠当前线程
线程的状态
synchronized
synchronized的特性
1、互斥
2、刷新内存
死锁
死锁的四个必要条件
避免死…目录
创建线程
方法一继承Thread类来创建一个线程类
方法二实现Runnable重写run
线程等待
获取当前线程引用
休眠当前线程
线程的状态
synchronized
synchronized的特性
1、互斥
2、刷新内存
死锁
死锁的四个必要条件
避免死锁
volatile关键字
wait方法
notify方法
定时器
线程池
线程池的创建
往线程池里添加任务 创建线程
方法一继承Thread类来创建一个线程类 class MyThread extends Thread{ Override public void run() { while(true) { System.out.println(1111); } } } public class Main { public static void main(String[] args) { //创建Mythread类的实例 MyThread myThreadnew MyThread(); //调用start方法启动线程 myThread.start(); } } 继承Thread使用匿名内部类 Thread t new Thread(){ Override public void run() { System.out.println(111); } }; t.start(); 方法二实现Runnable重写run class MyRunnable implements Runnable{ Override public void run() { while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Main { public static void main(String[] args) { Runnable runnablenew MyRunnable(); Thread t new Thread(runnable); t.start(); } } 使用Runnable和继承Thread方法的区别在于解耦合
TIPS
.start 和 .run方法的区别
.run代码中不会创建出新的线程只有一个主线程这个主线程里面只能依次执行循环执行完一个循环再执行另一个
我们可以使用IDEA或者jconsole来观察进程里多线程的情况 jconsole是jdk自带的工具我们在jdk的bin目录里可以找到
在启动jconsole前确保idea程序已经跑起来了如下会列出当前机器上运行的所有java进程 Thread-0就是我们新建的线程 如果要更明显的找到新线程我们在创建线程的时候给它设置一下名字 Thread t new Thread(()-{ while(true){ System.out.println(hello); } },这是新线程名字); t.start(); 线程属性
属性获取方法IDgetid()名称 getName() 状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isinterrupted()
这里的id是java分配的不是系统api提供的后台线程不结束不影响整个进程结束设置后台线程 t.setDaemon(true)前台线程没有执行结束整个进程是一定不会结束的isAlive判定内核线程是不是已经没了回调方法执行完毕线程就没了但是Thread对象可能还在。
lambda表达式有一个语法规则变量捕获是可以自动捕获到上层作用域中涉及到的局部变量所谓变量捕获就是让lambda表达式把当前作用域中的变量在lambda内部复制了一份
只能捕获一个final或者实际上是final的常量变量没有使用final但是没有进行修改 在java中并不能像C一样直接中断一个线程只能让线程任务做的快一点依靠标志位来决定具体示例如下 Thread t new Thread(()-{ while(!Thread.currentThread().isInterrupted()){ System.out.println(hello); } },这是新线程名字); t.start(); System.out.println(让t线程结束);t.interrupt(); 设置标志位的相关方法
方法说明 public void interrupt() 中断对象关联的线程如果线程正在阻塞则以异常方式通知否则设置标志位publi static boolean interrupted()判断当前线程的中断标志位是否设置调用后清除标志位public boolean isinterrupted()判断对象关联的线程的标志位是否设置调用后不清除标志位
线程等待
让一个线程等待另一个线程执行结束再继续执行本质就是控制线程结束的顺序
join线程等待核心方法
方法说明public void join()等待线程结束public void join(long millis)等待线程结束最多等millis毫秒public void join(long millis,int nanos)等待时间精度更高
主线程中调用t.join() 此时就是主线程等待t线程先结束 Thread t new Thread(()-{ for(int i0;i5;i) { System.out.println(t线程工作); } },这是新线程名字); t.start(); System.out.println(主线程开始等待);t.join(); System.out.println(主线程等待结束); 一旦调用join主线程就会发生阻塞一直阻塞到t执行完毕了没有设置等待时间的话join才会解除阻塞主线程才会继续执行
获取当前线程引用
方法说明public static Thread currentThread()返回当前线程对象的引用
休眠当前线程
方法说明public static void sleep(long millis)休眠当前线程millis毫秒public static void sleep(long millis,int nanos)更高精度
线程的状态
JAVA中有六个线程状态
NEW安排了任务还未开始行动RUNNABLE可工作的又可以分为正在工作中和即将开始工作也就是就绪状态BLOCKED排队等待由于锁竞争导致的阻塞WAITING排队等待由wait这种不固定时间的方式产生的阻塞TIMED_WAITING排队等待由于sleep这种固定时间的方式产生的阻塞TERMINATED工作完成了Thread对象还在内核中的线程已经没了
获取线程状态可以通过getState方法如下 System.out.println(t.getState()); t.start(); t.join(); System.out.println(t.getState()); synchronized
因为线程的调度顺序是不确定的这会导致线程不安全的情况因此我们需要引入锁再Java的一个对象对应的内存空间中除了自己定义的一些属性之外还有一些自带的属性对象头对象头中其中就有属性表示当前对象是否已经加锁
synchronized的特性
1、互斥
synchronized会起到互斥效果某个线程执行到某个对象的synchronized中时其他线程如果也执行到同一个对象synchronized就会阻塞等待。
进入synchronized修饰的代码块相当于加锁退出synchronized修饰的代码块相当于解锁
synchronized除了修饰代码块之外还可以修饰一个实例方法或者修饰一个静态方法
对于普通方法有两种写法
第一种 synchronized public void increase() { count; } 第二种 public void increase() { synchronized (this) { count; } } 这两种方法是一样的第一种可以看成第二种的简写 。
如果是修饰静态方法相当于是针对类对象加锁 class Counter{ public int count; //第一种方法 synchronized public static void increase() { } //第二种方法 public static void increase() { synchronized (Counter.class) { count; } } } 两种方法也是等价的。
2、刷新内存
synchronized的工作过程
获得互斥锁从主内存拷贝变量到最新副本工作的内存执行代码将更改后的共享变量的值刷新到主内存释放互斥锁
当出现如下代码 synchronized locker) { synchronized locker){ …… } } 3、可重入 第一次加锁假设能够加锁成功此时的locker就属于是“被锁定的”状态进行第二次加锁很明显locker已经是锁定状态了第二次加锁操作原则上是应该要“阻塞等待”的应该要等待到锁被释放才能加锁成功的 但是实际上一旦第二次加锁的时候阻塞了就会出现死锁情况。 因此synchronized的可重入性也就是记录当前持有锁的线程如果是持有锁的线程再进行加锁则允许加锁。但是释放锁是要到最外层结束才释放这里使用了引用计数锁对象中不仅要记录谁拿到了锁还要记录锁被加了几次每加锁一次计数器就1每解锁一次计数器就-1直到0才真正释放锁
死锁
死锁的四个必要条件
互斥使用(锁的基本特性)当一个线程持有一把锁之后另一个线程也想获取到锁就需要阻塞等待不可抢占锁的基本特性当锁已经被线程1拿到之后线程2只能等线程1主动释放不能强行抢占过来请求保持代码结构一个线程尝试获取多把锁先拿到锁1再拿锁2的时候锁1不释放循环等待/环路等待等待的依赖关系形成环
避免死锁
解决死锁破坏上面三、四条件即可
对于3来说调整代码结构避免写“锁嵌套”逻辑对于4来说可以约定加锁顺序就可以避免循环等待针对锁进行编号比如约定加多把锁的时候先加编号小的锁后加编号大的锁
volatile关键字
1、保证内存可见性 用volatile修饰的变量每次都从内存中读取值而不会因为编译器优化将常访问未修改的变量值读取到寄存器内存中对应变量值修改后程序还是访问的寄存器java中叫做工作内存的值从而导致“内存可见性”问题也是线程安全的问题。 关于内存可见性还涉及到一个关键概念JMMJava内存模型
2、禁止指令重排序
TIPS volatile和synchronized都能对线程安全起到一定的积极作用但是volatile不能保证原子性而synchronized保证原子性synchronized也能保证内存可见性。
wait方法
wait做的事情
使当前执行代码的线程进行等待把线程放到等待队列中释放当前的锁满足一定条件时被唤醒重新尝试获取这个锁
notify方法
notify时唤醒等待的线程
方法notify也要在同步方法或同步块中调用该方法是用来通知那些可能等待该对象的对象锁的其他线程对其发出通知并使它们重新获取该对象的锁如果多个线程等待则由线程调度器随机挑选出一个wait状态的线程没有先来后到 定时器
在标准库中java.util.Timer
如下是一个简单的使用示例
主线程执行schedule方法的时候就是把这个任务给放到timer对象中与此同时timer里头也包含一个线程这个线程叫做“扫描线程”一旦时间到扫描线程就会执行刚才安排的任务了Timer里可以安排多个任务的。 Timer timernew Timer(); timer.schedule(new TimerTask() { Override public void run() { System.out.println(时间到了); } },2000); System.out.println(程序开始); 此处传参使用匿名内部类的写法继承了TimerTask并且创建出一个实例TimerTask实现了Runnable接口以此来重写run方法通过run描述任务的详细情况。 public abstract class TimerTask implements Runnable 线程池
线程池就是把线程创建好放到池子里后续用的时候直接从池子里取这样效率会比我们每次都新创建线程效率更高。
那么为什么从线程池里取的效率会比新创建线程效率更高呢
线程池的创建
线程池对象不是我们直接new的而是通过一个专门的方法返回一个线程池对象如下 ExecutorService service Executors.newCachedThreadPool(); 这种方法就是采用工厂模式 Executors是一个工厂类newCachedThreadPool()是一个工厂方法。
newCachedThreadPool线程池里的线程用过之后不着急释放以备随时再使用此时构造出来的线程池对象有一个基本的特点线程数目是能够动态适应的随着往线程池里添加任务这个线程中的线程会根据需要自动被创建出来。
newFixedThreadPool这个工厂方法就是创建一个固定大小的线程池如下是创建一个包含3个线程的线程池 ExecutorService service Executors.newFixedThreadPool(3); 除了上述两种工厂方法还有 newScheduledThreadPool()newSingleThreadExecutor()这两种并不常用这里便不再介绍。
这几个工厂方法生成的线程池本质上都是对一个类ThreadPoolExecutor进行的封装。
ThreadPoolExecutor构造参数
int corePoolSize 核心线程数int maximumPoolSize 最大线程数
这两个参数描述了线程池中线程的数目这个线程池里线程的数目是可以动态变化的变化范围就是[corePoolSize,maximumPoolSize]
long keepAliveTime 允许非核心线程数存留时间TimeUnit unit 时间单位 mssminBlockingQueueRunable workQueue 阻塞队列用来存放线程池中的任务可以根据需要灵活设置这里的队列是啥需要优先级就可以设置PriorityBlockingQueue如果不需要优先级并且任务数目是相对恒定的可以使用ArrayBlockingQueue如果不需要优先级并且任务数目变动较大的使用LinkedBlockingQueueThreadFactory threadFactory 工厂模式的体现不用手动设置属性RejectedExecutionHandler handler 线程池的拒绝策略一个线程池能容纳的任务数量有上限当到达上线的时候做出什么样的反应如下是常用拒绝策略
ThreadPoolExecutor.AbortPolicy直接抛出异常ThreadPoolExecutor.CallerRunsPolicy新添加的任务由添加任务的线程负责执行ThreadPoolExecutor.DiscardOldestPolicy丢弃任务队列中最老的任务ThreadPoolExecutor.DiscardPolicy丢弃当前新加的任务
常见问题
使用线程池需要设置线程的数目数目设置多少合适 使用实验的方式对程序进行性能测试测试过程中尝试修改不同的线程池的线程数目看哪种情况更符合要求
往线程池里添加任务
通过submit往线程池里注册任务 service.submit(new Runnable() { Override public void run() { System.out.println(hello); } });