小言_互联网的博客

第四章Java核心类库_多线程

455人阅读  评论(0)

多线程

一.线程与进程

1.线程与进程

程序(program):

是为了完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。(程序是静态的)

进程(process):

是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。
进程是动态的,是一个动的过程,有它自身的产生、存在和消亡的过程。

线程(thread):

进程可以进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程。

2.线程调度

目的是为了更合理的利用CPU

分时调度:

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

抢占式调度Java使用的调度方式):

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
但其实,多线程程序并不能提高程序的运行速度但能够提高程序运行效率,让CPU的使用率更高。

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

1. 同步与异步

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

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

2. 并发与并行

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

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

三.继承Thread

1.代码块

Demo1

public class Demo1 {
   
    public static void main(String[] args) {
   
        MyThread myThread = new MyThread();
        myThread.start();
        for(int i = 0; i < 10; i++){
   
            System.out.println("我要睡觉!"+i);
        }
    }

MyThread

public class MyThread extends Thread{
   
    /**
     * run方法就是线程要执行的任务方法
     */
    @Override
    public void run() {
   
        //这里的代码就是一条新的执行路径
        //这个执行路径的触发方式不是调用run方法,
        //而是通过thread对象的start()来启动任务
        for(int i = 0; i < 10; i++){
   
            System.out.println("我要吃饭!"+i);
        }
    }
}

2.运行结果

3.时序图


补充:每个线程都有自己的栈空间,共用一份堆内存。

四.实现Runnable(推荐使用)

1.使用方法

另一种实现多线程的方法之步骤:
①创建自定义类implements实现Runnable接口,并重写run方法;
②用自定义类创建一个对象r;
③用Thread类创建一个对象t,并将r作为t构造方法的参数;

2.代码块

实现Runnable与继承Thread相比有如下优势
1,通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况;
2,可以避免单继承所带来的局限性(Java允许实现多个接口,但不允许继承多个父类);
3,任务与线程是分离的,提高了程序的健壮性;
4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程;

Demo1

public class Demo1 {
   
    public static void main(String[] args) {
   
        //实现Runnable
        //1.    创建一个任务对象
        MyRunnable r = new MyRunnable();
        //2.    创建一个线程,并为其分配一个任务
        Thread t = new Thread(r);
        //3.    执行这个线程
        t.start();
        for(int i = 0; i < 10; i++){
   
            System.out.println("我要睡觉!"+i);
        }
    }
}

MyRunnable

/**
 * 用于给线程执行的任务
 */
public class MyRunnable implements Runnable {
   
    @Override
    public void run() {
   
        //线程的任务
        for (int i=0;i<10;i++){
   
            System.out.println("我要上厕所"+i);
        }
    }
}

3.thread也有一定的好处

public class Demo1 {
   
    public static void main(String[] args) {
   
        //若只调用一次,用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);
        }
    }
}

五.Thread类

1.常用构造方法

2.其他常用方法


用于设置优先级的字段


六.设置和获取线程名称

MyRunnable

/**
 * 用于给线程执行的任务
 */
public class MyRunnable implements Runnable {
   
    @Override
    public void run() {
   
        //线程的任务
        //静态方法currentThread可以获得当前线程,调用getName/setName
        // 可以获得/设置线程的名称
        System.out.println(Thread.currentThread().getName());

    }
}

Demo1

public class Demo1 {
   
    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();
    }
}

运行结果

七.线程休眠sleep

public class Demo1 {
   
    public static void main(String[] args) throws InterruptedException {
   
        for (int i = 0; i < 10; i++){
   
            System.out.println(i);
            Thread.sleep(1000);
        }
    }
}

八.线程的中断

过时的stop方法可以直接中断线程,但是如果线程来不及释放资源,会造成一部分垃圾无法回收
这里采用添加中断标记的方法:调用interrupt方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码。

1.代码

public class Demo1 {
   
    public static void main(String[] args) throws InterruptedException {
   
        //线程的中断
        //一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
        Thread t = new Thread(new MyRunnable(), "子线程");
        t.start();
        for (int i =0;i < 5;i++){
   
            System.out.println(Thread.currentThread().getName()+":"+i);
            Thread.sleep(1000);
        }
        t.interrupt();//主线程for循环执行完毕后,将子线程中断
    }
    static class MyRunnable implements Runnable {
   
        @Override
        //这里的run方法不能将异常抛出,因为这里父接口Runnable没有声明异常的抛出
        //子接口不能声明比父接口范围更大的异常
        //所以只能进行try-catch
        public void run() {
   
           for (int i =1;i <= 10;i++){
   
               System.out.println(Thread.currentThread().getName()+":"+i);
               try {
   
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
   
                   System.out.println("我挂机了嘤嘤嘤~");
                   return;
               }
           }
        }
    }
    
}

2.运行结果

九.守护线程

概述:
线程分为守护线程和用户线程;
- 用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
- 守护线程:守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡;
直接创建的都是用户线程;
设置守护线程:线程对象.setDaemon(true),而且一定要在启动之前设置。

十.线程安全

线程不安全的原因:
多个线程争抢同一个数据,使得数据在判断和使用时出现不一致的情况。

解决方案1-同步代码块

public class Demo1 {
   
    public static void main(String[] args) throws InterruptedException {
   
        Object o = new Object();
        //线程不安全
        //解决方案1  同步代码块
        //格式:synchronized(锁对象){}
        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;
                    }
                }//synchronized结束
            }//while结束
        }//run结束
    }//Ticket结束
}//Demo1结束

解决方案2-同步方法

public class Demo1 {
   
    public static void main(String[] args) throws InterruptedException {
   
        Object o = new Object();
        //线程不安全
        //解决方案2  同步方法
        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 (true) {
   
                boolean flag = sale();
                if(!flag){
   
                    break;
                }
            }
        }
        public synchronized boolean 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;
            }
            return false;
 
        }
    }
}
 

给方法上锁,对应的锁对象就是this, 如果是静态修饰方法的话,锁对象为类名.class;
比如这里sale方法被修饰为静态方法的话,锁对象为Ticket.class,也就是字节码文件对象

解决方案3-显式锁Lock

同步方法和同步代码块都属于隐式锁显式锁则是程序员手动加锁、解锁

public class Demo{
   
    public static void main(String[] args) {
   
        Object o = new Object();
        //线程不安全
        //解决方案3   显示锁  Lock  子类 ReentrantLock
        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;
        //参数为true表示公平锁    默认是false 不是公平锁
        private Lock l = new ReentrantLock(true);
        @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();
            }
        }
    }
}

十一.公平锁与非公平锁

-区别
公平锁:先来先得,遵循排队;
非公平锁:大家一起抢(同步代码块,同步方法,显式锁都属于非公平锁);

-实现方法
在显式锁实例化时,传入参数true()

//参数为true表示公平锁    默认是false 不是公平锁
private Lock l = new ReentrantLock(true);

十二.死锁典例

public class Demo {
   
    /**
     * 死锁典例:两个线程互相申请对方的锁,但是对方都不释放锁。
     * @param args
     */
    public static void main(String[] args) {
   
        //线程死锁
        Culprit culprit = new Culprit();
        Police police = new Police();
        
        //新建一个线程,警察对罪犯说话,等待罪犯回应
        //警察的say方法里面打印完自己说的话之后,便等待罪犯的回应
        //而另一边罪犯的say方法也在等待警察的回应,且罪犯的say是带锁的
        //故罪犯的reaction方法一直到不了,警察的say就一直在等罪犯的reaction
        new MyThread(culprit,police).start();

        //main主线程,罪犯对警察说话,等待警察回应
        //罪犯的say方法里面打印完自己说的话之后,便等待警察的回应
        //而另一边警察的say方法也在等待罪犯的回应,且警察的say是带锁的
        //故警察的reaction方法一直到不了,罪犯的say就一直在等警察的reaction
        culprit.say(police);
        
    }

    static class MyThread extends Thread{
   
        //私有两个属性,一个警察p一个罪犯c
        private Culprit c;
        private Police p;
        //两参的构造方法
        public MyThread(Culprit c, Police p) {
   
            this.c = c;
            this.p = p;
        }
        @Override
        public void run() {
   
            p.say(c);//警察对罪犯说话,等待罪犯回应
        }
    }

    static class Culprit{
   
        public synchronized void say(Police p){
   
            System.out.println("罪犯说:你放了我,我放了人质");
            p.reaction();
        }
        public synchronized void reaction(){
   
            System.out.println("罪犯说:好的,我放了人质,你放过我");
        }
    }

    static class Police{
   
        public synchronized void say(Culprit c){
   
            System.out.println("警察说:你放了人质,我放过你");
            c.reaction();
        }
        public synchronized void reaction(){
   
            System.out.println("警察说:好的,我放了你,你把人质给我");
        }
    }


}

解决方案:
在任何有可能导致锁产生的方法里,不要再调用另外一个方法让另外一个锁产生;
一个方法已经产生一个锁了,就不要再去找其他有可能产生锁的方法了。

十三.多线程通信问题

主要借助于wait和notify函数实现

十四.生产者与消费者

思路:

厨师cook为生产者线程,服务员waiter为消费者线程,食物为生产与消费的物品;

假设目前只有一个厨师,一个服务员,一个盘子。理想状态是:厨师生产一份饭菜,服务员端走一份,且饭菜的属性未发生错乱;

厨师可以制作两种口味的饭菜,制作100次;

服务员可以端走饭菜100次。

Test one:

public class TestOne{
   
    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.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
   
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }//else结束
            }//for结束
        }//run结束
    }//Cook结束

    //服务员
    static class Waiter extends Thread{
   
        private Food f;
        //构造方法
        public Waiter(Food f) {
   
            this.f = f;
        }
        //上100次菜
        @Override
        public void run() {
   
            for (int i = 0; i < 100; i++) {
   
                try {
   
                    Thread.sleep(100);//端完一次休息一下:因为厨师在setNameAndTaste中有休眠100毫秒
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
                f.get();//端菜
            }//for结束
        }//run结束
    }//Waiter结束

    //食物
    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;
        }
        // 消费
        public void get(){
   
            System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        }//get结束

    }//Food结束


}//TestOne结束

 

运行结果

原因分析:在厨师进行setNameAndTaste的时候,先设置了name,此时休眠了0.1秒,而taste还未来得及切换,服务员此时就把菜端出去了,出现了多线程协同合作的不匹配问题。

Test two:

给get、setNameAndTaste两个方法都加上同步标记synchronized,让厨师在进行setNameAndTaste时候服务员别进来瞎掺和进行get,服务员get的时候厨师别进来瞎掺和进行setNameAndTaste。

错误原因分析:synchronized只是确保了方法内部不会发生线程切换,但并不能保证生产一个消费一个的逻辑关系。

Test three:

终极解决方案:
厨师做完饭后喊醒服务员,自己睡着;服务员送完饭后喊醒厨师,自己睡着。主要修改的部分为setNameAndTaste与get方法。

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;
        //做完了把标记改为false
        flag = false;
        //唤醒在当前this对象下所有睡着的线程(即唤醒服务员)
        this.notifyAll();
        //厨师自己睡过去
        try {
   
            this.wait();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}//setNameAndTaste结束

// 消费
public synchronized void get(){
   
    if (!flag){
   
        System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        //端完了把标记改为true
        flag = true;
        //唤醒在当前this对象下所有睡着的线程(即唤醒厨师)
        this.notifyAll();
        //服务员自己睡过去
        try {
   
            this.wait();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}//get结束

运行结果

十五.线程的六种状态

十六.线程创建的第三种方式-带返回值的线程Callable

新的创建线程的方式。之前的创建线程方式:实现Thread的子类、实现Runnable接口,可以看成是和主线程并发执行的;这里要讲的线程更像是主线程指派的一个任务,主线程可以获得其返回值。
后续开发使用较少,仅作了解,不再赘述。

十七.线程池 Executors

为什么需要线程池?

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

后续开发中使用到线程池的地方很少,不是重点,不再赘述,详情看老师发的pdf。

十八.Lambda表达式

为什么要用lambda表达式?
答: 对于某些应用场景,我们更注重于结果,如果能用一个方法解决, 那么通过创建对象、调用方法的方式可能会更加繁琐。

1)不使用lambda

public class Demo1 {
   
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
   
        print(new MyMath() {
   
            @Override
            public int sum(int x, int y) {
   
                return x + y;
            }
        }, 100, 200);
    }
    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);
    }
}

2)使用lambda
不需要实现接口、实例化对象;

public class Demo1 {
   
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
   
        print((int x, int y) -> {
   
                return x + y;
            }, 100, 200);
    }
    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);
    }
}
 

参考链接:https://blog.csdn.net/qq_41528502/article/details/108052873


转载:https://blog.csdn.net/tangyuan__/article/details/115495219
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场