ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

多线程的部分知识

2021-07-25 16:31:56  阅读:178  来源: 互联网

标签:余票 run Thread 知识 出票 线程 new 多线程 部分


提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

本文章主要总结学习Java多线程的一些知识(本文章内容为本人参加开课吧对某部分的知识总结)


提示:以下是本篇文章正文内容,下面案例可供参考

一、多线程的基础知识

  1. 进程与线程

进程是指一个内存中运行的应用程序,而且每个线程都有自己独立的内存空间。
线程是进程中的的一个执行路径,可理解为一个进程可包含多个线程。线程之间可以自由切换,并发执行,一个进程最少要有一个线程。
以下这个任务管理器的截图可以表示:当前电脑249进程中一共有三千多个线程。
电脑任务管理器中所显示的进程与线程

  1. 线程调度
    线程调度分为分时调度和抢占式调度。
    分时调度就是平均分配每个线程占用CPU的时间。
    抢占式调度就是优先让优先级高的线程先使用,如果优先级一样,则随机选择其一。而Java用的就是抢占式调度。

  2. 同步与异步
    同步:排队执行,效率低但数据安全
    异步:同时执行,效率高但数据不安全

  3. 并发与并行
    并发:两个或多个事情在同一个时间段内发生,比如说:吃饭睡觉敲代码都可以在一天内发生,它们就是并发的。
    并行:两个或多个事情在同一时刻发生(同时发生),比如说:吃饭的时候同时可以追剧。

  4. 线程阻塞:
    所有消耗较长时间的操作都会产生线程阻塞,例如:一个线程需要读取某个文件需要十秒,那么这十秒就是线程阻塞。

二、如何在Java中实现多线程

  1. 继承Thread类实现多线程并重写其run方法,但需要用start方法来触发多线程。

创建MyThread来继承Thread类并重写run
MyThread类代码如下(示例):

public class MyThread extends Thread{
    /**
     * run方法就是线程要执行的任务方法
     */
    @Override
    public void run(){
        //一条新的执行路径
        //不调用run,调用start
        for(int i=0; i<10; i++){
            System.out.println("他好" + i);
        }
    }
}

在测试类中测试MyThread类是否实现多线程
测试类代码如下(示例):

public static void main(String[] args) {
        //Thread实现多线程
        MyThread m = new MyThread();
        m.start();
        for(int i=0; i<10; i++){
            System.out.println("你好"+i);
        }
}

测试结果:
在这里插入图片描述

  1. 实现Runnable接口从而实现多线程
    创建MyRunnable并实现Runnable接口:
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0; i<10; i++){
            System.out.println("你好" + i);
        }
    }
}

测试类代码如下:

//实现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);
        }

Runnable与继承Thread相比的优势:
1. 通过创建任务,然后给线程分配的方式实现多线程,更适合多个线程同时执行相同任务的情况
2. 可以避免单继承的局限性
3. 任务与线程本身分离
4. 线程池接受Runnable但不接受Thread

3.线程的中断
线程的中断首先要给要中断的线程一个中断标记,当该线程获取到中断标记时,由程序员判断是否继续执行程序。例如:可以利用return直接杀死该线程。
代码如下:

    public static void main(String[] args) {
        //一个线程是一个独立的执行路径,是否结束由其自身决定
        Thread t1 = new Thread(new MyRunnable2());
        t1.start();
        for(int i=0; i<7; i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);//获取线程的名字
            try {
                Thread.sleep(100);//线程休眠0.1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //给t1线程添加中断标记
        t1.interrupt();
    }

    static class MyRunnable2 implements Runnable{

        @Override
        public void run() {
            for(int i=0; i<10; i++){
                System.out.println(Thread.currentThread().getName() + ":" + i);//获取线程的名字
                try {
                    Thread.sleep(100);//线程休眠0.1秒
                } catch (InterruptedException e) {
                    System.out.println("哈哈");
                    return;//执行死亡
                }
            }
        }
    }

4.守护线程
当一个进程不包含任何活着的线程时,用户线程进行结束。
当最后一个用户线程结束后,所有守护线程自动死亡。

//守护线程当最后一个用户线程结束时,才死亡
Thread t1 = new Thread(new MyRunnable3());
//设置t1为守护线程
t1.setDaemon(true);

线程不安全问题及其解决方法
线程不安全代码示例:

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

测试结果:
在这里插入图片描述
出现线程不安全的原因:
暂设线程为a,b,c。当卖票时,a先判断(count > 0)若成立则进去遍历,这时候b和c紧跟着a立刻判断(count > 0)是否成立(b和c的判断发生在线程a执行count- -这条代码之前)。但当余票剩余最后一张时,a线程执行的判断条件成立,最后一张票售出。但因为上述原因,b和c已经进入循环,就会出现0-1 的情况,从而发生多卖票的情况。

解决方法1:同步代码块
当设置了锁,每个线程执行前都会先查看是否有锁,若有锁则进行等待。等同于三个人同时抢一个洗手间,一个人抢到后,其余两人必须等待。但以下代码可能难以表现这个性质,因为当第一个线程抢到锁后,它比其他线程会更接近锁,所以下一次也会被第一个线程抢到。

 public static void main(String[] args) {
        //线程不安全
        //解决方法1. 同步代码块
        //格式: synchronized(锁对象){}, 任何对象都可以成为锁
        Runnable run = new SecOne.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) {
                    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:同步方法
可以简单理解为方法1的if代码块中的内容提出来创建一个方法并用 synchronized 进行修饰从而实现锁。那么方法中的锁分为两种:

  1. 方法若为静态方法,那么锁就是类名.class,代码例子中的就是Ticket2.class
  2. 若不为静态方法,那么锁就是this
 public static void main(String[] args) {
        //线程不安全
        //解决方法2. 同步方法
        Runnable run = new Ticket2();
        //线程创建
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }

    static class Ticket2 implements Runnable{
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            while (true) {
                boolean f = sale();
                if(!f)
                    break;
            }
        }

        public synchronized boolean sale(){
            //若为静态方法,锁为Ticket2.class
            //否则为this
            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;

        }
    }

部分测试结果:

Thread-0出票成功,余票:2
卖票中
Thread-0出票成功,余票:1
卖票中
Thread-0出票成功,余票:0

第二种锁的详解:若将三个线程创建的代码块改为以下代码:

new Thread(new Ticket2()).start();
new Thread(new Ticket2()).start();
new Thread(new Ticket2()).start();

那么测试结果就为:

Thread-0出票成功,余票:1
卖票中
Thread-2出票成功,余票:1
卖票中
Thread-1出票成功,余票:1
卖票中
Thread-0出票成功,余票:0
Thread-1出票成功,余票:0
Thread-2出票成功,余票:0

这种情况就是每个线程都有自己的锁,都有自己要执行的方法互不干涉。所以并没有实现线程安全。也可以突出表现非静态方法的锁就是this(此时调用方法的对象)。

解决方法3:显式锁 Lock 子类 ReentrantLock(前两种方法都是隐式锁)
显式锁比隐式锁更有上锁和解锁的概念,并且锁对象更能体现面向对象的机制。

 public static void main(String[] args) {
        //线程不安全
        //解决方法3. 显式锁 Lock 子类 ReentrantLock
        Runnable run = new Ticket3();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }

    static class Ticket3 implements Runnable{
        private int count = 10;
        //显示锁: 参数为true为公平锁
        private Lock lock = new ReentrantLock(true);
        @Override
        public void run() {
            while (count>0) {
                lock.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;
                }
                lock.unlock();//解锁
            }

        }
    }

公平锁:先来先到,有排队的概念。
非公平锁:抢到锁就执行。
可以在创建锁对象时,传递一个boolean对象来确定是否实现公平锁。
公平锁锁对象示例:

private Lock lock = new ReentrantLock(true);

公平锁的部分运行结果:

Thread-0出票成功,余票:5
卖票中
Thread-2出票成功,余票:4
卖票中
Thread-1出票成功,余票:3
卖票中
Thread-0出票成功,余票:2
卖票中
Thread-2出票成功,余票:1
卖票中
Thread-1出票成功,余票:0

公平锁锁对象示例:
默认值为false,所以可以不传递。

private Lock lock = new ReentrantLock();

非公平锁的部分运行结果:

Thread-0出票成功,余票:2
卖票中
Thread-0出票成功,余票:1
卖票中
Thread-0出票成功,余票:0

标签:余票,run,Thread,知识,出票,线程,new,多线程,部分
来源: https://blog.csdn.net/Giroro2020/article/details/119081379

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有