多线程部分学习笔记

1.线程与进程

1.1概念

  • 进程

    是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

  • 线程

    是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少

    有一个线程

线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分

成若干个线程

  • 线程阻塞

    休眠sleep()方法;或执行某行读取文件的代码;或执行等待输入的代码。

    又称耗时操作,总之停在那里就算

1.2分时调度

  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    抢占式调度

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),

    Java使用的为抢占式调度。

  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,

    只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时

    刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使

    用率更高。

2.同步与异步&并行与并发

  • 同步:排队执行 , 效率低但是安全.

  • 异步:同时执行 , 效率高但是数据不安全.

  • 并发:指两个或多个事件在同一个时间段内发生。

  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

3.Thread

多线程常用实现方法之一:继承Thread类

public static void main(String[] args){
    MyThread m = new MyThread();
    m.start();
    for(int i=0;i<10;i++){
    	System.out.println("线程二执行"+i);
	}
}

public class MyThread extends Thread{
    //run()方法就是线程要执行的任务,但该线程的触发方式,不是调用run(),而是通过Thread对象的start()方法来启动任务
    @Override
    public void run(){
        for(int i=0;i<10;i++){
        	System.out.println("线程一执行"+i);
        }
    }
}

4.Runnable

多线程常用实现方法之二:实现Runnable接口

public static void main(String[] args){
    //1.创建一个任务对象
    MyRunnable r = new MyRunnable();
    //2.创建一个线程,并为其分配一个任务
    Thread t = new Thread(r);
    //3.执行这个线程
    t.start();
    //也可简化成new Thread(new MyRunnable()).start();
    //main中的线程二,用于作对比体现线程同时执行
    for(int i=0;i<10;i++){
    	System.out.println("线程二执行"+i);
    }
}
//用于给线程执行的任务
public class MyRunnable implements Runnable{
    @Override
    public void run(){
        for(int i=0;i<10;i++){
        	System.out.println("线程一执行"+i);
        }
    }
}

Runnable相比Thread的优点:

a.通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况,更灵活;

b.可避免单继承带来的局限性,java只可以单继承,但可以多实现;

c.任务与线程本身是分离的,提高了程序的健壮性;

d.后续学习的线程池技术,接受Runnable类型的任务单不接收Thread类型的线程。

那就不用Thread了吗?也不全是,它有一种简便的实现线程方法:

public static void main(String[] args){
    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);
	}
}

5.Thread类

Thread类常用方法:

1.Thread构造方法:
Thread(Runnable target);
//可定义线程的名字,还有get方法可获取名字
Thread(Runnable target,String name);
Thread(String name);
2.setPriority()//设置线程的优先级
3.sleep(long millis)//使当前正执行的线程休眠(比如想隔多长时间输出一下...)sleep(1000)——每隔一秒
4.setDaemon(boolean on)//将此线程标记为Daemon(守护)线程或用户线程
//守护线程是依附与用户线程的,用户线程没了它也没了
5.设置和获取线程名称
System.out.println(Thread.currentThread().getName());
//currentThread()方法可获取当前线程
//getName()方法可获取线程名称

6.线程中断

一个线程是一个独立的执行路径,它是否应该结束,应该由其本身决定。

也就是说不应该由外部强行中断,否则会导致该部分内存无法释放。


7.守护线程

线程分为用户线程和守护线程。

用户线程:

守护线程:


8.线程安全

经典案例:多线程抢票问题

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);
        }
    }
}

该代码有可能出现不合理情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9YGfCc6h-1618823572231)(C:\Users\jy\AppData\Roaming\Typora\typora-user-images\image-20210417172546374.png)]

发生原因:main方法中同时执行了三条线程,有可能当count=1的时候,某一线程抢到了,进入run方法的循环,但还未执行到count–的时候,有其他线程抢到了时间片,也进入了循环,则可能有多个线程同时进入到循环,最后余票减到负数。

我们加了个sleep()是为了让这种异常出现的可能性增大以示说明。

9.线程安全处理方法

9.1方法一:同步代码块。

给run方法用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;
    Object o = new Object();
    @Override
    public void run(){        
        //Object o = new Object();
        //此处是一个误区,如果new对象o放在run里面,则线程不安全问题解决不了,因为每个线程执行的时候都自己创一把锁,不是同一把锁就无法排队。
        while(true){
            synchronized(o){
                //三个线程必须同时在执行一个任务,故前面new了个Object o
                if(count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }else{
                        break;//count=0时要结束
                    }
                    count--;
                    System.out.println("出票成功,余票:"+count);
                }
            }
        }
    }
}

9.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;
    @Override
    public void run(){  
        whiel(true){
            boolean flag = sale();
            if(!flag){
                break;
            }
        }
    }
    
    //将同步部分写成方法
    public synchronized boolean sale(){
    //加上synchronized修饰符就可以实现排队了
        if(count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }else{
                        break;//count=0时要结束
                    }
                    count--;
                    System.out.println("出票成功,余票:"+count);
            return true;
                }
        return false;
    }                            

这种将同步操作写成方法,锁是谁?

是调用此方法的this对象

验证方法:main部分原来只new了一个对象,现改成new三个对象

public static void main(String[] args){
    
    //Runnable run = new Ticket();
    //new Thread(run).start();
    //new Thread(run).start();
    //new Thread(run).start();
    new Thread(new Ticket()).start();
    new Thread(new Ticket()).start();
    new Thread(new Ticket()).start();
}

则每次抢票会同时弹出三个打印,线程间不是排队的,因为是new Ticket()在调用,此处是三个Ticket对象分别调用,则没排队。得是同一个对象,运行三个线程才可排队。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M8uXXdYX-1618823572234)(C:\Users\jy\AppData\Roaming\Typora\typora-user-images\image-20210417212719988.png)]

9.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;
    //显式锁
    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("出票成功,余票:"+count);
            }else{
                break;
            }
            l.unlock();//结束后手动解锁
        }                    
    }
}

10.公平锁与非公平锁

公平锁是先来先到,解锁后按到来的顺序执行,也就是有在等解锁的时候有排队的过程。

非公平锁就是解锁后大家抢。

上述三种方法默认是非公平锁

实现公平锁:新建显式锁时传入参数true。

private Lock l = new ReentrantLock(true);

11.线程死锁

形象描述:警察说你放了人质我放了你。罪犯说你放了我我放了人质,两个。陷入死循环。

避免方法:在所有有可能导致锁产生的方法里,不用再调用另外一个方法,导致另外的锁产生。

示例:


12.多线程通信问题

生产者与消费者问题

package com.java.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo4  {

    /**
     * 多线程通信问题, 生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndSaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndSaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                //服务员先把厨师唤醒
                this.notifyAll();
                //服务员再让自己睡过去
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

执行结果:厨师生产一份服务员端走一份,一份煎饼果子一份老干妈,且味道不对应错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMhTpW9c-1618823572236)(C:\Users\jy\AppData\Roaming\Typora\typora-user-images\image-20210417234142150.png)]

13.线程的六种状态

new(创建了但未执行)、Blocked(阻塞,也即线程排队的状态)、Runnable(执行)、Waiting、TimeWaiting(定时等待)、Terminated(退出)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5T2dOYDo-1618823572237)(C:\Users\jy\AppData\Roaming\Typora\typora-user-images\image-20210418003125746.png)]

14.Callable

比较特殊的一种线程创建方式

之前的创建方式是主线程和其他线程并行,callable则是既可以并行也可以作为主线程的一个任务,主线程等它执行完了再接着执行。

既然主线程要等它执行完,那还要多线程有何用?——如果这种callable同时有多个进行,就有用,且能体现主次关系。

非并行实现的具体方法:主线程调用Callable.get()方法获取返回值:Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

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();

15.线程池

不作为重点学习,知道有这么个东西就行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mr2wTnWm-1618823572241)(C:\Users\jy\AppData\Roaming\Typora\typora-user-images\image-20210419113224173.png)]

15.1缓存线程池

/**
\* 缓存线程池. 
\* (长度无限制) 
\* 执行流程: 
\* 1. 判断线程池是否存在空闲线程 
\* 2. 存在则使用 
\* 3. 不存在,则创建线程 并放入线程池, 然后使用 
*/ 
ExecutorService service = Executors.newCachedThreadPool(); 
//向线程池中 加入 新的任务 
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()); 
    } 
});

15.2定长线程池

/**
\* 定长线程池. 
\* (长度是指定的数值) 
\* 执行流程:
\* 1. 判断线程池是否存在空闲线程 
\* 2. 存在则使用 
\* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
\* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
*/ 
ExecutorService service = Executors.newFixedThreadPool(2); 
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()); 
    } 
}); 

15.3单线程线程池

效果与定长线程池 创建时传入数值1 效果一致. 
/**
\* 单线程线程池. 
\* 执行流程: 
\* 1. 判断线程池 的那个线程 是否空闲 
\* 2. 空闲则使用 
\* 4. 不空闲,则等待 池中的单个线程空闲后 使用 
*/ 
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()); 
    } 
}); 

15.4周期定长线程池

public static void main(String[] args) { 
    /**
    \* 周期任务 定长线程池. 
    \* 执行流程: 
    \* 1. 判断线程池是否存在空闲线程 
    \* 2. 存在则使用 
    \* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    \* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
    *
    \* 周期性任务执行时: 
    \* 定时执行, 当某个时机触发时, 自动执行某任务 .
    */ 
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 
    /**
    \* 定时执行 
    \* 参数1. runnable类型的任务 
    \* 参数2. 时长数字 
    \* 参数3. 时长数字的单位 
    */ 
    /*service.schedule(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,TimeUnit.SECONDS); 
    */
    /**
    \* 周期执行 
    \* 参数1. runnable类型的任务 
    \* 参数2. 时长数字(延迟执行的时长) 
    \* 参数3. 周期时长(每次执行的间隔时间) 
    \* 参数4. 时长数字的单位 
    */ 
    service.scheduleAtFixedRate(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,2,TimeUnit.SECONDS); 
}

16.Lambda表达式

是函数式编程思想,不关注过程只注重结果,我实现功能只是需要方法即可,有时候没必要创建个对象再调用方法。与面向对象思想相对。

在多线程这块这个方法的简化功能能够特别体现。

public static void main(String[] args){
    //1.面向对象思想方法:匿名内部类(匿名内部类都已经是简化过了的),还是较为冗余
    Thread t = new Thread(new Runnable(){
        @Override
        public void run(){
            System.out.println("锄禾日当午");         
        }
    });
    t.start();
    
    //2.用Lambda表达式,函数式编程思想
    Thread t = new Thread(() -> {       
            System.out.println("锄禾日当午");         
    });
    t.start();
    //效果是完全一样的,等于省掉了创建对象的过程
}

ad(future).start();


### 15.线程池

不作为重点学习,知道有这么个东西就行。

[外链图片转存中...(img-mr2wTnWm-1618823572241)]

15.1缓存线程池

```java
/**
\* 缓存线程池. 
\* (长度无限制) 
\* 执行流程: 
\* 1. 判断线程池是否存在空闲线程 
\* 2. 存在则使用 
\* 3. 不存在,则创建线程 并放入线程池, 然后使用 
*/ 
ExecutorService service = Executors.newCachedThreadPool(); 
//向线程池中 加入 新的任务 
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()); 
    } 
});

15.2定长线程池

/**
\* 定长线程池. 
\* (长度是指定的数值) 
\* 执行流程:
\* 1. 判断线程池是否存在空闲线程 
\* 2. 存在则使用 
\* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
\* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
*/ 
ExecutorService service = Executors.newFixedThreadPool(2); 
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()); 
    } 
}); 

15.3单线程线程池

效果与定长线程池 创建时传入数值1 效果一致. 
/**
\* 单线程线程池. 
\* 执行流程: 
\* 1. 判断线程池 的那个线程 是否空闲 
\* 2. 空闲则使用 
\* 4. 不空闲,则等待 池中的单个线程空闲后 使用 
*/ 
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()); 
    } 
}); 

15.4周期定长线程池

public static void main(String[] args) { 
    /**
    \* 周期任务 定长线程池. 
    \* 执行流程: 
    \* 1. 判断线程池是否存在空闲线程 
    \* 2. 存在则使用 
    \* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    \* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
    *
    \* 周期性任务执行时: 
    \* 定时执行, 当某个时机触发时, 自动执行某任务 .
    */ 
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 
    /**
    \* 定时执行 
    \* 参数1. runnable类型的任务 
    \* 参数2. 时长数字 
    \* 参数3. 时长数字的单位 
    */ 
    /*service.schedule(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,TimeUnit.SECONDS); 
    */
    /**
    \* 周期执行 
    \* 参数1. runnable类型的任务 
    \* 参数2. 时长数字(延迟执行的时长) 
    \* 参数3. 周期时长(每次执行的间隔时间) 
    \* 参数4. 时长数字的单位 
    */ 
    service.scheduleAtFixedRate(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,2,TimeUnit.SECONDS); 
}

16.Lambda表达式

是函数式编程思想,不关注过程只注重结果,我实现功能只是需要方法即可,有时候没必要创建个对象再调用方法。与面向对象思想相对。

在多线程这块这个方法的简化功能能够特别体现。

public static void main(String[] args){
    //1.面向对象思想方法:匿名内部类(匿名内部类都已经是简化过了的),还是较为冗余
    Thread t = new Thread(new Runnable(){
        @Override
        public void run(){
            System.out.println("锄禾日当午");         
        }
    });
    t.start();
    
    //2.用Lambda表达式,函数式编程思想
    Thread t = new Thread(() -> {       
            System.out.println("锄禾日当午");         
    });
    t.start();
    //效果是完全一样的,等于省掉了创建对象的过程
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值