1.多线程
1.1易混概念
进程:拥有独立内存空间的应用程序,每个进程的内存空间是独立的
线程:是进程中的执行路径,共享内存空间,线程之间可以自由切换,进 程至少包含一个线程
线程实际上是进程基础之上的进一步划分,一个进程启动后,里面若干执行路径又可以划分若干线程
线程阻塞(所有耗时操作):所有比较消耗时间的操作。例:读取文件,接收用户输入。
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
每个线程都有自己的栈空间,但是共用堆内存
同步:排队执行,效率低但是安全。
异步:同时执行,效率高但是数据不安全
并发:在指定的时间段内同时发生的事件
并行:同一刻发生,同时发生。
1.2线程的六种状态
1.new:线程刚被创建,还未启动时的状态。
2.Runnable:正在执行
3.Blocked:阻塞
4.Waiting:休眠
5.TimedWaiting:指定时间的休眠
6.Terminated:线程结束(死亡)
1.3线程的创建与执行方法
1.3.1创建及执行
创建方式:总共三种.
(1)创建自己的类继承Thread类,重写run方法(写进去自己要执行的内容)最后执行时,调用Thread类的start()方法。
(2)创建自己的类实现Runnable接口,重写run方法(写进去自己要执行的内容),执行时:创建自己写的类的对象、Thread类对象,将自己的类对象名传入Thread参数列表内,Thread类调用start()方法。
(3)创建自己的类实现Callable接口,实现call方法(写进去自己要执行的内容),执行时:创建自己写的类的对象、FutureTask类的对象、Thread类对象,将自己的类对象名传入FutureTask参数列表内,Thread类调用start()方法。
具体创建操作:
第一种:Thread
public static void main(String[] args) {
/**
* 创建对象并执行
* 调用start()方法执行
*/
MyThread myThread = new MyThread();
myThread.start();
}
public static class MyThread extends Thread{
/**
* 重写run方法
*/
@Override
public void run() {
System.out.println("要进行的操作");
}
}
第二种:Runnable+Thread
public static void main(String[] args) {
/**
* 创建对象并传任务,然后执行
* 调用start()方法执行
*/
//任务对象
MyRunnable myRunnable = new MyRunnable();
//线程对象,分配任务
Thread t = new Thread(myRunnable);
t.start();
}
public static class MyRunnable implements Runnable{
/**
* 实现run方法
*/
@Override
public void run() {
System.out.println("要进行的操作");
}
}
第三种:Callable+Thread
public static void main(String[] args) {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
}
public static class MyCallable implements Callable<Integer> {
/**
* 实现call方法
* @return
* @throws Exception
*/
@Override
public Integer call() throws Exception {
System.out.println("要进行操作的地方");
//返回值根据泛型可以调整
return 0;
}
}
1.3.2三种方法的对比
实现Runnable与继承Thread相比
优势:
1-创建任务的方式然后给线程分配,来实现多线程,更适合多个线程同时执行相同任务
2-可以避免单继承所带来的局限性
3-任务与线程本身是分离的,提高了健壮性
4-线程池技术,接受Runnable类型的任务,而不接受Thread类型的任务。
Callable和Runnable相同点:
1-都是接口
2-都可以编写多线程程序
3-都采用Thread.start()启动
Callable和Runnable不同点:
1-Runnable没有返回值,Callable有返回值。
2-Runnale接口的run()不能抛出异常,Callable的call()能抛出异常。
Callable获取返回值:
Callable接口支持返回执行结果,通过FuturTask.get()获取,此方法会阻塞主线程执行,不调用就不会阻塞。
1.4线程安全问题
1.同步代码块
格式:synchronized(锁对象){}
锁对象通常定义一个Object对象。用大括号,括起来要锁的位置上
2.同步方法
以方法为单位进行加锁
直接给方法添加synchronized
它的锁是这个方法的this,如果是静态方法,锁的是:类名.class
3.显式锁
Lock类 ,子类:ReentrantLock()
使用方法:
Lock l = new ReentrantLock();
l.lock();//锁
.
.要锁住的内容
.
.
l.unlock();//开锁
2.线程池Executors
为什么要有线程池:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池好处:
1-降低资源消耗
2-提高响应速度(不用每次都创建线程)直接拿线程池里的线程
3-提高线程的可管理性
2.1缓存线程池
无限制长度
任务加入后的执行流程:
1.判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用
ExecutorService e = Executors.newCachedThreadPool();
//指挥线程执行任务
e.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
}
});
e.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
}
});
e.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
}
});
//缓存1000ms
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
//第四次执行的线程是缓存的之前的线程
e.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
}
});
2.2定长线程池
长度是指定的线程池
加入任务后的执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
//长度固定为了2(只能两个线程同时执行)
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"操作");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
2.3单线程线程池
执行多少次都是一个线程
执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈");
}
});
2.4周期任务定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
/**
*定时执行
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:参数2的时间单位 Timeunit的常量指定
/* scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"嘿嘿");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
/*
周期性执行任务
参数1:任务
参数2:延迟时长数字
参数3:周期时长数字(每隔多久执行一次)
参数4:时长数字的单位
*/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);