目录
多线程基础前言
●进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的。
● 进程调用系统api,申请系统分配资源(Java进程就是分配Java内存空间, 通过JVM虚拟机初始化共享区域)
●线程:线程也存在并发.并行(单个CPU时间片轮转、一个时间点,多个CPU上的真同时)
一、创建线程
1.匿名内部类
2.继承Thread类
3.实现Runnable接口
class Main{
public static void main(String[] args) {
MyThread myThread=new MyThread();
// myThread.start();//匿名内部类
myThread.run();//run方法直接调用,不会启动线程,只是在线程main中,调用run方法
new Thread(new MyRunnable()).start();
}
}
class MyThread extends Thread {//继承Thread类
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable{//实现Runnable接口
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
二、线程基本API
1.线程的让步
Thread.yield();//运行态–>就绪态
public class ThreadYield {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
while(Thread.activeCount()>1){//使用调试的方法运行
Thread.yield();
}
System.out.println(Thread.currentThread().getName());
}
}
2.线程的等待
(1)join
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
t.start();
//先将T线程执行完毕,再往下执行
t.join();
t.join(2000);
System.out.println(Thread.currentThread().getName());
}
(2)结合activeCount()+yield();使用
public class ThreadYield {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
while(Thread.activeCount()>1){
Thread.yield();
}
System.out.println(Thread.currentThread().getName());
}
}
3.线程中断
(1)isInterrupted()
(2)nterrupted():不是真实的直接中断,而是告诉某个线程需要中断,具体是否中断由该线程自己决定
(3)interrupt()
(4)过期方法stop();
注意:
1.线程启动以后:中断标志位=false
2.在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来进行中断的处理逻辑。通过方法thread.isInterrupted()/Thread.interrupted()
3.线程因调用wait()/join()/sleep()处于阻塞状态时,将线程中断,会造成:
(1)在这三个阻塞方法所在的代码行,直接抛InterruptedException异常
(2)抛出异常之后,重置线程的中断标志位(=true)
4.static void interrupted():返回中断标志位,并重置标志位
void isInterrupted():返回中断标志位
5.自定义的标志位满足不了线程处于阻塞状态时,中断操作
public class InterruptThread {
//中断一个线程,但是线程没有处理中断
public static void test1(){
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true){
}
}
});
t.start();
t.interrupt();
}
//
public static void test2() throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {//线程运行状态下处理中断标志
for(int i=0;i<50;i++){
System.out.println(i+""+Thread.currentThread().isInterrupted());
}
// while (!Thread.currentThread().isInterrupted()){
while (!Thread.interrupted()){
System.out.println(Thread.currentThread().getName());
}
}
});
t.start();//t线程中的中断标志位=false
t.interrupt();//t线程中的中断标志位=true
}
public static void test3() throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().isInterrupted());//true
//线程处于调用wait()/join()/sleep()阻塞的时候,如果把当前线程中断,会直接抛出一个异常
//阻塞状态时,通过捕获及处理异常,来处理线程的中断逻辑
//抛出异常以后线程的标志位被重置
Thread.sleep(9999999);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().isInterrupted());//false
}
}
});
t.start();//t线程中的中断标志位=false
t.interrupt();//t线程中的中断标志位=true
}
public static void test4() throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().isInterrupted());
// System.out.println(Thread.interrupted());//返回中断标志位,并重置标志位
}
}
});
t.start();//t线程中的中断标志位=false
t.interrupt();//t线程中的中断标志位=true
}
private static volatile boolean IS_INTERRUPTDE;
//使用自定义的中断标志位
public static void test5(){
Thread t=new Thread(new Runnable() {
@Override
public void run() {
//自定义的标志位 能满足线程处于运行态的中断操作
// while (!IS_INTERRUPTDE){
// System.out.println(Thread.currentThread().getName());
// }
//自定义的标志位满足不了西安城处于阻塞状态时,中断操作
try {
Thread.sleep(99999);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
IS_INTERRUPTDE=true;
}
public static void main(String[] args) throws InterruptedException {
//test1();
//test2();
// test3();
// test4();
test5();
}
}
4.守护线程
●至少有一个非守护线程没有被销毁,进程就不会退出
●非守护线程一般可以称为工作线程,守护线程可以称为后台线程
public class DaemonThread {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(99999999L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//设置线程为守护线程
t.setDaemon(true);
t.start();
}
}
5.线程的启动
run()和start()
MyThread myThread=new MyThread();
myThread.start();
//run 方法直接调用,不会启动现线程,只会在当前main线程中调用run方法
//myThread.run();
三、多线程的运行效率
使用多线程提高效率需要考虑的因素
(1)所有线程执行是并发+并行
(2)线程创建、销毁是比较耗时
(3)线程的调度由系统决定(线程越多,系统调度越频繁;线程就绪态转变为运行态,也是有性能及时间消耗)
(4)单个线程运行的任务量
四、线程的安全问题
1.原子性
卖票操作
●A-1和A-2不具有原子性,导致在代码行之间插入了并发/并行执行的其他代码(B-1)
●造成的结果:业务逻辑处理出现问题
特殊的原子性代码
(分解执行存在编译为class文件时,也可能存在cpu执行指令) :
(1) n++, n–, ++n, --n都不是原子性: 需要分解为三条指令:从内存读取变量到CPU,修改变量,写回内存
(2)对象的new操作: Object obj = new Object(); 分解为三条指令:分配对象的内存,初始化对象,将对象赋值给变量
2.可见性
new Thread(new Runnable() {
@override
public void run() {
for(int j=0; j<COUNT; j++){
SUN++;//1.从主内存中将SUM变量复制到线程的工作内存
} //2.在工作内存中修改变量(+1操作)
} //3.将SUM变量从线程工作内存写回主内存
}).start();
主内存SUM=0;
●A-1
●A-2 (+1操作—>线程A的工作内存中, SUM=1,主内存SUM=0)
●B-1(从主内存复制SUM=0)
●B-2 (+ 1操作—>线程B的工作内存中,SUM=1,主内存SUM=0) ●B-3 (线程B中的SUM变量,将值写回主内存SUM=1)
●A-3 (线程A中的SUM变量,写回主内存SUM=1)
●造成线程不安全:共享变量发生了修改的丢失(线程B的++操作,发生丢失)
3.重排序
●线程内代码是JVM、CPU都进行重排序,给我们的感觉是线程内的代码是有序的,是因为重排序优化方案会保证线程内代码执行的依赖关系
●线程内看自己的代码运行,都是有序,但是看其他线程代码运行,都是无序的。
五、synchronized关键字
语法使用:
1.静态方法.上
2.实例方法
3.代码块: synchronized(对象){
new的对象.class都是对象
// TODO
*进入synchronized代码行时,需要获取对象锁:
1.获取成功:往下执行代码
2.获取失败:阻塞在synchronized代码行
退出synchronized代码块,或synchronized方法:
1.退回对象锁
2.通知JVM及系统,其他线程可以来竞争这把锁
synchronized加锁操作的关注点:
1.对哪一个对象进行加锁一一个对象只有一把锁
2.只有同一一个对象,才会有同步互斥的作用(多线程线程安全的三大特性部能够满足)
3.对于synchronized内的代码来说,在同一个时间点,只有一个线程在运行(没有井发、并行)
4.运行的线程数量越多,性能下降越快(归还对象锁的时候,就有越多的线程不停的在被唤醒。阻塞状态切换)
5.同步代码执行时间越短,性能下降也较快
多线程操作考虑:1.安全2.效率
在保证安全的前提条件下,尽可能的提高效率:
1.代码执行时间比较长,考虑多线程(线程的创建。销毁的时间消耗)
2.如果不能保证安全,所有代码都没有意义一-先保证安全, 再保证效率
六、volatile关键字
(1)保证可见性 (2)保证有序性
注意点:
1.不能保证原子性
2.volatile修饰的变量,进行赋值不能依赖变量(常量赋值可以保证线程安全)
3.使用场景: volatile可以结合线程加锁的一些手段,提高线程效率 只是变量的读取、常量赋值,可以不加锁,而是使用volatile, 可以提高效率
七、单例模式
三种实现方式
1.饿汉模式
public class Sington {
private Sington(){
}
private static Sington SINGTON=new Sington();
public static Sington getInstance(){
return SINGTON;
}
2.懒汉模式
public class Sington {
private Sington(){
}
private static Sington SINGTON;
public static Sington getInstance() {
if (SINGTON == null) {
SINGTON = new Sington();
}
return SINGTON;
}
3.双重校验锁
原理:
1.volatile关键字修饰变量
2.私有构造方法
3.双重校验锁的写法保证线程安全
public class Sington {
private Sington(){
}
private static volatile Sington SINGTON;
public static Sington getInstance() {
if (SINGTON == null) {//提高效率:变量使用Volatile可以保证可见性
synchronized (Sington.class){
//保证单例模式:返回是同一个对象
if(SINGTON==null){
//三条指令:分配内存,初始化对象,赋值给变量
SINGTON=new Sington();
}
}
}
return SINGTON;
}
}
八、线程的状态
九、wait/notily0/notifyAIl()
生产者/消费者模型
public class BreadOperator {
// 库存面包数量:上限100,下限0
public static volatile int SUM;
public static void main(String[] args) {
// 启动生产者线程,生产面包
for(int i=0; i<5; i++){
new Thread(new Producer(), "面包师傅"+i).start();
}
//启动消费者线程,消费面包
for(int i=0; i<5; i++){
new Thread(new Consumer(), "消费者"+i).start();
}
}
// 默认生产者:面包师傅生产面包,一次生产3个面包,每个面包师傅生产20次
private static class Producer implements Runnable{
@Override
public void run() {
try {
for(int i=0; i<20; i++){
synchronized (BreadOperator.class){
//生产完以后,库存>100是不行,所以库存在97以上就不能生产
while (SUM + 3 > 100){
//释放对象锁,需要让其他线程进入同步代码块,当前线程需要进入阻塞
BreadOperator.class.wait();
}
SUM += 3;//生产面包
Thread.sleep(10);
BreadOperator.class.notify();
System.out.println(Thread.currentThread().getName()+", 生产了,库存为:"+SUM);
}
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 默认消费者:消费面包,一次消费一个面包,消费者一直消费
private static class Consumer implements Runnable{
@Override
public void run() {
try {
while (true){
synchronized (BreadOperator.class){
//库存为0,不能继续消费,阻塞当前线程(不能继续消费)
while (SUM == 0){
BreadOperator.class.wait();
}
SUM--;
Thread.sleep(10);
//notify()/notifyAll()都是通知调用wait()被阻塞的线程
//notify随机唤醒一个wait()阻塞的线程,notifyAll()唤醒全部wait()阻塞的线程
//在synchronized代码块结束,也就是释放对象锁之后,才会唤醒
//等于说,synchronized结束之后,wait()和synchronized代码行阻塞的线程,都会被唤醒
BreadOperator.class.notify();
System.out.println(Thread.currentThread().getName()+", 消费了,库存为:"+SUM);
}
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
十、阻塞式队列
利用数组和循环队列实现阻塞式队列
/*
* 实现阻塞式队列:
* 1.满足线程安全的生产、消费的功能
* 2.生产、消费达到上限/下限时,需要阻塞等待
* */
public class MyBlockQueue<E> {
private Object[] item;
private int takeIndex;//弹出元素的索引位置
private int putIndex;//添加元素的索引位置
private int size;//有效容量
public MyBlockQueue(int capacity){
item=new Object[capacity];
}
//线程间通信,推荐做法:
//1.whlie来判断,不要使用if(因为在判断代码中进行wait释放锁以后,其他线程会修改变量,再次wait恢复的时候,条件已经不满足)
//使用notifyAll方法,通知所有wait被阻塞的线程
public synchronized void put(E e) throws InterruptedException {
while (size==item.length){//达到上限需要等待
wait();
}
putIndex=(putIndex+1)%item.length;//存放元素的索引++,需要满足循环队列索引》数组长度的情况
item[putIndex]=e;//存放元素
size++;
notifyAll();
}
public synchronized E take() throws InterruptedException {
while (size==0){//达到下限,需要等待
wait();
}
takeIndex=(takeIndex+1)%item.length;
size--;
notifyAll();
return (E) item[takeIndex];
}
public static void main(String[] args) {
MyBlockQueue<Integer> queue=new MyBlockQueue<>(100);
for(int i=0;i<5;i++){
final int k=i;//匿名内部类需要使用被final修饰的变量
new Thread(new Runnable() {
@Override
public void run() {
try {
for(int j=0;j<100;j++){
queue.put(k*100+j);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
while (true){
int num= 0;
try {
num = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num);
}
}
}
十一、线程池
现实模型对比:
●快递公司(线程池)可以接收很多的快递任务(可以>4, Runnable任务类) ,如果员工(线程池中创建的线程)没有空闲(正在干活.忙碌)。快递包裹就到快递公司的仓库中(线程地内部的一个属性,阻塞队列) .员工不停从仓库中取包裹,送快递,如果仓库中没有包裹,员工就等待,一直等到有包裹再派送(执行Runnable对象的run方法)
1.创建线程池
创建方式
① ExecutorService pool = new ThreadPoolExecutor();
②ExecutorService pool=Executors.newFixedThreadPool(4);//固定大小线程池,给定员工数量没有零时工
③ExecutorService pool=Executors.newSingleThreadExecutor();//数量为一的固定大小的线程池,就一个员工
④ ExecutorService pool=Executors.newScheduledThreadPool(4);//定时任务线程池:有正式员工,有零时员工
⑤ExecutorService pool=Executors.newCachedThreadPool();//缓存线程池:无正式员工全部为临时工(外包公司)
参数
corePoolSize,// 核心线程数(正式员工):创建好线程池,正式员工就开始取快递
// 临时工雇佣:正式员工忙不过来,就会创建临时工
// 临时工解雇:空闲时间超出设置的时间范围,就解雇
maximumPoolSize ,// 最大线程数(最多数量的员工:正式员工+临时工)
keepAliveTime ,// 时间数量
TimeUnit.SECONDS,// 时间单位(时间数量+时间单位表示一定范围的时间)
new ArrayBlockingQueue<>(1000), // 阻塞队列:存放包裹的仓库(存放任务的数据结构)
new ThreadFactory() { // 线程池创建Thread线程的工厂类。没有提供的话,就使用线程池内部默认的创建线程的方式
@Override
public Thread newThread(Runnable r) {
return null;
}
},
new ThreadPoolExecutor.DiscardOldestPolicy() // 拒绝策略:
// CallerRunsPolicy:谁(execute代码行所在的线程)让我(快递公司)送快递,不好意思,你自己去送
// AbortPolicy:直接抛出异常RejectedExecutionException
// DiscardPolicy:从阻塞队列丢弃最新的任务(队尾)
// DiscardOldestPolicy:从阻塞队列丢弃最旧的任务(队首)
2.实现自己的线程池
import lesson6.MyBlockQueue;
public class MyThreadPool {
private MyBlockQueue<Runnable> queue;
public MyThreadPool(int size,int capacity){
queue=new MyBlockQueue<>(capacity);
//创建正式员工
for (int i=0;i<size;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true){//正式员工一直运行
Runnable task=queue.take();
//从仓库取包裹:
// 1.成功取出包裹(任务),方法返回
//2.仓库中取不出包裹(其他员工正在取阻塞在synchronized代码行;没有包裹了)
task.run();
//正式员工送快递(实例方法的调用来执行任务)
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
public void execute(Runnable task){
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyThreadPool pool=new MyThreadPool(5,100);
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("A");
}
});
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("B");
}
});
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("C");
}
});
}
}
十二、定时器
1.jdk的时间操作
(1) Date
Date date1 = new Date();//无参构造方法:返回系统当前时间
Date date2 = new Date(99999999);//以格林威治时1970-01-01开始,经过给定时间数量的毫秒
(2)DateFormat
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
(3)System时间获取
从1970-01-01开始到当前时间点经过的毫秒数
long current = System.currentTimeMillis();
(4)JDK原生提供日期处理器
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("起床了");
}
};
new Timer().schedule(task, 3000, 1000);
2.自己实现定时器
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
/**
* 定时器:
* 1.在约定好的时间点上,执行某个任务
* 2.间隔时间到了,不停的执行任务
*/
public class MyTimer {
private BlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue();
public MyTimer(int count){
for(int i=0; i<count; i++) {
new Thread(new MyWorker(queue)).start();
}
}
/**
* 定时任务
* @param task 需要执行的任务
* @param delay 从当前时间延迟多少毫秒,执行任务
* @param period 间隔时间:<=0就忽略掉,>0需要每间隔给定时间,就执行任务
*/
public void schedule(Runnable task, long delay, long period){
try {
queue.put(new MyTimerTask(task, System.currentTimeMillis()+delay, period));
synchronized (queue){
queue.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class MyWorker implements Runnable{
private BlockingQueue<MyTimerTask> queue;
public MyWorker(BlockingQueue<MyTimerTask> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
//blockingQueue本身就是线程安全的,所以这里的方法调用不用放在同步代码块
MyTimerTask task = queue.take();
synchronized (queue) {
long current = System.currentTimeMillis();
if (task.next > current) {
queue.wait(task.next-current);
queue.put(task);
} else {
task.task.run();
if (task.period > 0) {
task.next = task.next + task.period;
queue.put(task);
}
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class MyTimerTask implements Comparable<MyTimerTask>{
//定时任务
private Runnable task;
//下次执行的时间
private long next;
private long period;
public MyTimerTask(Runnable task, long next, long period){
this.task = task;
this.next = next;
this.period = period;
}
@Override
public int compareTo(MyTimerTask o) {
return Long.compare(next, o.next);
}
}
十三、多线程使用场景
CPU执行指令:(1)计算指令 (2)逻辑指令 (3)数据拷贝
某Java进程,程序可能偏向
(1)计算密集型任务 (2)IO密集型任务
1.执行比较耗时的操作时,使用多线程
2,执行阻塞式代码时,会对当前线程造成阻塞时
十四、对比线程和进程
线程优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务 比特科技
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程和进程的区别
- 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
- 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
- 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
- 线程的创建、切换及终止效率更高。