多线程技术概述
线程与进程
进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
- 线程实际上是在进程基础上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分为若干个线程。
线程调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换,对于CPU的核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对于我们的感觉要快,看上去就是在同一时刻运行,其实,多线程程序并不能提高程序的运行速度,但是能够提高程序运行效率,让CPU的使用率更高。
同步与异步
同步:排队执行,效率低但是安全。
异步:同时执行,效率高但是不安全。
并发与并行
并发:指两个或多个事件在同一时间内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
继承Thread
在java中实现多线程程序
//Thread
pulic class MyThread extends Thread{
/**
*run方法就是线程要执行的任务方法
*/
public void run(){
//这里的代码,就是一条新的执行路径
//这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
public class Demo{
public static void main(String[] args){
MyThread m=new MyThread();
m.start();
for(int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
}
每个线程都拥有自己的栈空间,共用一分堆内存。
实现Runnable
实现Runnable与继承Thread相比有如下优势:
1.通过创建任务,然后线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
2.可以避免单继承所带来的局限性。
3.任务和线程本身是分离的,提高了程序的健壮性。
4.后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程。
/**
*用于给线程进行执行的任务
*/
public class MyRunnable implements Runnable{
@Override
public void run(){
//线程的任务
for(int i=0;i<10;i++){
System.out.println("床前明月光"+i);
}
}
}
//匿名内部类
//此方法也是继承Thread
new Thread(){
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("一二三四五"+i);
}
}
}.start();
for(int i=0;i<10;i++){
System.out.println("六七八九十"+i);
}
public static void main(String[] args){
//实现Rnnable
//1.创建一个任务对象
MyRunnable r=new MyRunnable();
//2.创建一个线程,并为其分配一个任务
Thread t=new Thread();
//3.执行这个线程
t.start();
for(int i=0;i<10;i++){
System.out.println("疑是地上霜"+i);
}
}
Thread类
stop方法已过时,不能使用,是不安全的。
字段
构造方法
方法
设置和获取线程名称
public static void main(String[] args){
System.out.println( Thread.currentThread().getName());
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
//也可以创建Thread对象进行启动,设置名称
Thread t=new Thread(new MyRunnable());
t.start();
t.setName();
}
static class MyRunnable implements Runnable(){
@Override
public void run(){
//获取当前线程
Thread.currentThread().getName();
}
}
线程休眠sleep
//需要处理异常
for(int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000);
}
线程阻塞
所有比较消耗时间的操作,如文件读取。
线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
public static void main(String[] args){
Thread t1=new Thread(new MyRunnable()).start();
t1.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
//给线程t1添加中断标记
t1.interrupt();
}
static class MyRunnable implements Runnable(){
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
// e.printStackTrace();
System.out.println("发现了中断标记,但是我们不死亡");
//自己return表示返回方法,结束线程
System.out.println("发现中断标记,我们自杀");
return;
}
}
}
}
守护线程
线程:分为守护线程和用户线程。
用户线程:当一个进程不包括任何的存活的用户线程时,进行结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
注:直接创建的线程时用户线程
public static void main(String[] args){
Thread t1=new Thread(new MyRunnable()).start();
//设置为守护线程
t1.setDaemon(true);
t1.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
//给线程t1添加中断标记
t1.interrupt();
}
线程安全问题
线程不安全,例如:买票
//线程不安全
public static void main(String[] args){
Runnable run=new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable(){
private int count=10;
@Override
public void run(){
while(count>0){
//卖票
System.out.println("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
//运行结果会出现-1,-2的情况
}
}
}
同步代码块和同步方法都属于隐式锁。
线程安全1-同步代码块
格式: synchronized(锁对象){
}
public static void main(String[] args){
Runnable run=new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable(){
private int count=10;
private Object o=new Object();
@Override
public void run(){
//Object o=new Object();要看同一把锁
while(true){
synchronized(o){
if(count>0){
//卖票
System.out.println("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName+"出票成功,余票:"+count);
}else{
break;
}
}
}
}
}
线程安全2-同步方法
public static void main(String[] args){
Runnable run=new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable(){
private int count=10;
private Object o=new Object();
@Override
public void run(){
while(true){
boolean flag=sale();
if(!flag){
break;
}
}
}
public synchronized void sale(){
if(count>0){
//卖票
System.out.println("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName+"出票成功,余票:"+count);
return true;
}else{
return false;
}
}
线程安全3-显示锁Lock
public static void main(String[] args){
Runnable run=new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable(){
private int count=10;
//显示锁 l
private Lock l=new ReentrantLock();
@Override
public void run(){
while(true){
l.lock();
if(count>0){
//卖票
System.out.println("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName+"出票成功,余票:"+count);
}else{
break;
}
l.unlock();
}
}
}
显示锁和隐式锁的区别:
一、层面不同
synchronized:Java中的关键字,是由JVM来维护的,是JVM层面的锁。
-
synchronized底层是通过monitorenter进行加锁
底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。并且只有在同步块或同步方法中,JVM才会调用monitory对象的,才可以调用wait/notify等方) -
通过monitorexit来退出锁
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API,是API层面的锁。 -
lock是通过调用对应的API方法来获取锁和释放锁的。
二、使用方式不同
- synchronized
程序能够自动获取锁和释放锁。Sync是由系统维护的,如果非逻辑问题的话话,不会出现死锁。 - Lock
需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
手动获取锁方法:lock.lock()。释放锁:unlock方法。并且需要配合tyr/finaly语句块来完成。
三、等待是否可中断
- synchronized
不可中断,除非抛出异常或者正常运行完成。 - Lock
可以中断的。
中断方式
- 调用设置超时方法tryLock(long timeout ,timeUnit unit)
- 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
四、加锁的时候是否可以设置成公平锁
- synchronized
只能为非公平锁。 - lock:两者都可以的。默认是非公平锁
在其构造方法的时候可以传入Boolean值。true:公平锁、false:非公平锁
五、锁绑定多个条件来condition
- synchronized
不能精确唤醒线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程。 - Lock
用来实现分组唤醒需要唤醒的线程,可以精确的唤醒。
六、性能区别
- synchronized
托管给JVM执行,Java1.5中,由于需要调用操作接口,可能导致加锁消耗时间过长,与Lock性比性能低。1.6以后,语义定义更加清晰,有适应自旋、锁粗化、锁消除、轻量级锁、偏向锁等,可进行许多优化,性能提高了,与Lock差不多。 - Lock
java写的控制锁的代码,性能高。
公平锁和非公平锁
公平锁:当线程锁解开时,先来先执行,有一个排队的流程。
非公平锁:当线程锁解开时,一块抢占。
//公平锁
//显示锁 l:fair参数为true,就表示是公平锁
private Lock l=new ReentrantLock(true);
线程死锁
public static void main(String[] args){
Culprit c=new Culprit();
Polic p=new Polic();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c=c;
this.p=p;
}
@Override
public void say(){
p.say(c);
}
}
//罪犯
static class Culprit{
public synchronized void say(Polic p){
System.out.println("罪犯:你放了我,我放人质");
p.fun();
}
public synchronized void fun()[
System.out.println("罪犯被放走了,罪犯也放了人质");
]
}
//警察
static class Polic{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我就放过你");
c.fun();
}
public synchronized void fun(){
System.out.print("警察救了人质,但是罪犯跑了");
}
}
//运行结果
//罪犯:你放了我,我放人质
//警察:你放了人质,我就放过你
多线程通信问题
多线程通信问题,生产者与消费者问题
生产者与消费者
public static void main(String[] args){
Food f=new Food();
new Cook(f).start();
new Waiter(f).start();
}
/**
*会出现的问题:
*线程紊乱 本应该是:
* 服务员端走的菜的名称是:老干妈小米粥,味道是:甜辣味
* 但是实际出来的是:
* 服务员端走的菜的名称是:老干妈小米粥,味道是:甜辣味
*原因:厨师刚设置好set方法,名称刚设置好,服务员就进行了get方法端菜,导致不协调的问题
*/
//厨师
static class Cook extends Thread{
private Foood();
public Cook(Food f){
this.f=f;
}
@Overrride
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else{
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
ststic class Waiter extends Thread{
private Food();
public Waiter(Food f){
this.f=f;
}
@Override
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food {
private String name;
private String taste;
public void SetNameAndTaste(String name,String taste){
this.name=name;
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
this.taste=taste;
}
private void get(){
System.out.println("服务员端走的菜是:"+name+"味道是:"+taste);
}
}
/**
*如果使用synchronized 排队来解决问题,会出现一个更乱的问题
*当厨师制作完一道菜时,锁解开,但是注意这个锁不是公平锁,厨师也会抢占,又开始做饭
*会发现有些菜没有被端走
*
*/
//厨师
static class Cook extends Thread{
private Foood();
public Cook(Food f){
this.f=f;
}
@Overrride
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else{
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
ststic class Waiter extends Thread{
private Food();
public Waiter(Food f){
this.f=f;
}
@Override
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food {
private String name;
private String taste;
public synchronized void SetNameAndTaste(String name,String taste){
this.name=name;
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
this.taste=taste;
}
private synchronized void get(){
System.out.println("服务员端走的菜是:"+name+"味道是:"+taste);
}
}
/**
*解决方法:当厨师做菜时,服务员睡着,做完饭唤醒服务员上菜;当服务员上菜,厨师睡着,服务员上完菜,唤醒厨师做饭
*
*/
//厨师
static class Cook extends Thread{
private Foood();
public Cook(Food f){
this.f=f;
}
@Overrride
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else{
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
ststic class Waiter extends Thread{
private Food();
public Waiter(Food f){
this.f=f;
}
@Override
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(100);
}catch(InterruptException e){
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food {
private String name;
private String taste;
//true 表示可以生产
private boolean flag=true;
public synchronized void SetNameAndTaste(String name,String taste){
if(flag){
this.name=name;
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
this.taste=taste;
flag=false;
this.notifyAll();
try{
this.wait();
}catch(InterruptException e){
e.printStackTrace;
}
}
}
private synchronized void get(){
if(!flag){
System.out.println("服务员端走的菜是:"+name+"味道是:"+taste);
flag=true;
this.noifyAll();
try{
this.wait();
}catch(InterruptException e){
e.printStackTrace();
}
}
}
}
线程的六种状态
线程状态
- NEW 尚未启动的线程处于此状态
- RUNNABLE 在Java虚拟机中执行的线程处于此状态
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态
- WAITING 无限期等待另一个线程执行特定操作的线程处于此状态
- TIMED_WAITING 正在等待另一个线程执行最多等待时间的操作的线程出于此状态
- TERMINATED 已退出的线程处于此状态
状态转换
带返回值的线程Callable
Runnable 与 Callab
//接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
Callable使用
//1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
//2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
//3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
public static void main()extends ExcutionException,InterruptedException{
Callable<Integer> c=new Callable();
FutureTask<Integer> task=new FutureTake<>(c);
//task.isDone() 判断这个线程的任务是否执行完毕
//task.cancel() 取消任务
new Thread(task).start();
//如果不调用get方法会一直执行下去
Integer j=task.get();
System.out.println(j);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implement Callable<Integer>{
@Override
public Integer call() throws Exception{
//Thread.sleep(3000);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(i);
}
return 100;
}
}
线程池概述
线程流程
- 创建流程
- 创建任务
- 执行任务
- 关闭流程
线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
Java中的四种线程池 . ExecutorService
缓存线程池
(长度无限制)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程 并放入线程池, 然后使用
/**
*缓存线程池
*(长度无限制)
*任务加入后的执行流程:
* 1.判断线程池是否存在空闲空闲流程
* 2.存在则使用
* 3.不存在,则创建线程并放入线程池,然后使用
*/
public static void main(String[] args){
ExecutorService service=Executors.newFixedThreadPool();
//指挥线程池中执行新的任务
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
}
定长线程池
(长度是指定的数值)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
/**
*定长线程池
*(长度是指定的数值)
*任务加入后的执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public static void main(String[] args){
ExecutorService service=Executors.newCachedThreadPool(2);
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
}
单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
执行流程:
1. 判断线程池 的那个线程 是否空闲
2. 空闲则使用
3. 不空闲,则等待 池中的单个线程空闲后 使用
/**
*定长线程池
*(长度是指定的数值)
*任务加入后的执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public static void main(String[] args){
ExecutorService service=Executors.newCachedThreadPool(2);
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currectThread().getName()+"锄禾日当午");
}
});
}
周期性任务定长线程池
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
周期性任务执行时:
定时执行, 当某个时机触发时, 自动执行某任务 。
/**
*周期任务 定长线程池
*执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4.不存在空闲线程,且线程池已满的情况下,则等待线程池存入空闲线程
*
*周期性任务执行时:
* 定时任务,当某个时机触发时,自动执行某任务。
*/
public static void main(String[] args){
SchedulExecutorService service=Executors.newCachedThreadPool(2);
/**
*定时执行一次
*参数1:定时执行的任务
*参数2:时长数字
*参数3:时长数字的时间单位,TimeUnit的常量指定
service.schedule(new Runnable(){
@Override
public void run(){
System.out.println("锄禾日当午");
}
},5,TimeUnit.SECONDS); */
/**
*周期性执行任务
*参数1:任务
*参数2:延迟时长数字(第一次执行在什么时间以后)
*参数3:周期时长数字(每隔多久执行一次)
*参数4:时长数字单位
*/
service.scheduleAtFixedRate(new Runnable(){
@Override
public void run(){
System.out.println("汗滴禾下土");
},5,1,TimeUnit.SECONDS);
}
Lambda表达式
函数式编程思想
面向对象:创建对象对用方法解决方法。
public static void main(String[] args){
//冗余的Runnable代码
/*MyRunnable r=new MyRunnable();
Thread t=new Thread(r);
t.start();
//匿名内部类
Thread t=new Thread(new Runnable()){
@Override
public void run(){
System.out.println("锄禾日当午");
}
});
t.start();
*/
//Lambda表达式
Thread t=new Thread(()-> {
System.out.println("锄禾日当午")
});
t.start();
}
static class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println)("锄禾日当午");
}
}
public static void mian(String[] args){
print(new MyMath(){
@Override
public int sum(int x,int y){
return x+y;
}
},200,300);
print((int x,int y)->{
return x+y;
},200,300);
}
public static void print(MyMath m,int x,int y){
int num=m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sum(int x,int y);
}
注:
显示锁和隐式锁的区别参考自:日常自闭的HXH
链接:https://blog.csdn.net/qq_43570075/article/details/106243873