美文网首页
【Java学习】多线程入门|创建线程|实现线程同步

【Java学习】多线程入门|创建线程|实现线程同步

作者: 榆野铃爱 | 来源:发表于2019-08-18 20:32 被阅读0次

    心得感悟

    多线程的基本概念还是很容易理解的,以前只知道软件可以同时运行,今天终于了解了它的实现机制,还是有点意思的。多线程入门是比较简单,进门后再往前走那就有点难了,所以今天先不讲线程安全。


    内容简概

    • 一、多线程介绍
    • 二、多线程的状态和生命周期
    • 三、为什么要创建⼦线程
    • 四、创建线程的两种⽅式
    • 五、实现线程同步的两种方式

    具体内容

    一、多线程介绍

    再说线程之前,我们先来了解一下进程。什么是进程呢?进程就是正在运行的程序。你可以按下Ctrl + Shift + ESC键调出任务管理器,里面会显示出当前正在运行的进程(见图一)。应用名称最后的小括号内的数字表示当前进程包含的线程个数,你也可以展开列表查看具体的线程(见图二)。

    一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    拿下载文件、视频举例,当你要下载多个文件或视频时,若是单线程,只有当一个下载任务执行完后,才能执行另一个下载任务;若是多线程,可以多个下载任务“同时”执行,这样大大提高了程序的运行效率,让CPU的使用率更高。

    图一:任务管理器
    图二:具体的线程

    二、多线程的状态和生命周期

    状态名              定义
    创建状态(new) 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
    就绪状态(runnable) 调用了start()方法, 等待CPU进行调度
    运行状态(running) 执行run()方法
    阻塞状态(blocked) 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
    死亡状态(terminated) 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
    多线程的生命周期

    三、为什么要创建⼦线程

    如果在主线程中存在比较耗时的操作,如下载视频、数据处理,这些操作会阻塞主线程,后面的任务必须等这些任务执行完后才能执行。为了不阻塞主线程,需要将耗时的任务放在子线程中去处理。

    四、创建线程的两种⽅式

    1. 第一种方式:Thread

    写一个类,使它继承Thread

    class TestThread extends Thread{
    
        // 写一个类继承Thread
        // 实现run方法
        // 方法里面是具体要执行的代码
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name);
            for (int i =1 ; i <= 100; i++) {
                System.out.println(name + ":" +i);
            }
            super.run();
        }
    }
    

    使用方式:使用Thread操作这个任务,然后用setName()给进程命名,用start()开始进程。

            Thread t = new Thread(pt);
            t.setName("子线程1");
            t.start();
    
    2. 第二种方式:Runnable

    写一个类,使它实现Runnable接⼝

    class MyThread implements Runnable{
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    

    使用方式①:使用Thread操作这个任务,然后用setName()给进程命名,用start()开始进程。

            Thread t = new Thread(mt);
            t.setName("子线程1");
            t.start();
    

    使用方式②:使用匿名对象,然后直接在run()中写入执行语句,同样用setName()给进程命名,用start()开始进程。这个方式仅适用于只使用一次这个任务的情况。

            Thread t = new Thread(new Runnable() {
               @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            });
            t.setName("子线程3");
            t.start();
    

    使用方式③:创建线程的同时直接开启线程任务,不需要操作线程对象本身,但是无法为这个进程命名。

            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(Thread.currentThread().getName()+";"+i);
                    }
                }
            }).start();
    

    使用方式④:使用Lambda表达式,但事实不建议使用,因为阅读性差。

          new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+";"+i);
                }
            }).start();
    

    五、实现线程同步的两种方式

    1. 第一种方式:synchronized

    使⽤synchronized同步代码块。我们写一个卖火车票的程序。

    public class lock {
        public static void main(String[] args){
            Ticket ticketCQ = new Ticket("重庆");
            Thread t1 = new Thread(ticketCQ);
            t1.start();
    
            Ticket ticketSH = new Ticket("上海");
            Thread t2 = new Thread(ticketSH);
            t2.start();
        }
    }
    
    class Ticket implements Runnable{
        //定义所有车票的数量
        public static int num = 100;
        String name;
    
        //定义
        public Ticket(String name){
            this.name = name;
        }
    
        static final Object obj = new Object();
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                // 判断有没有票
                synchronized (obj){
                    // 需要同步的代码
                    if (num > 0) {
                        System.out.println(name + "出票:" + num);
                        num--;
                    }
                    else {
                        break;
                    }
                }
            }
        }
    }
    

    也可以用同步方法实现,同步方法的本质其实就是同步代码块。只需将部分代码改为如下代码即可。

    @Override
        public void run() {
            synchronized (this){
                test();
            }
        }
    
        public synchronized void test() {
            for (int i = 1; i <= 100; i++) {
                // 判断有没有票
                synchronized (obj){
                    // 需要同步的代码
                    if (num > 0) {
                        System.out.println(name + "出票:" + num);
                        num--;
                    }
                    else {
                        break;
                    }
                }
            }
        }
    
    2. 第二种方式:ReentrantLock

    使⽤ReentrantLock同步

    class Ticket implements Runnable{
    
        //定义所有车票的数量
        public static int num = 100;
        String name;
    
        //定义
        public Ticket(String name){
            this.name = name;
        }
    
        //创建一个可重复的锁
        static ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                // 判断有没有票
    
                    // 加锁
                    lock.lock();
                    //需要同步的代码
                    if (num > 0) {
                        System.out.println(name + "出票:" + num);
                        num--;
                        lock.newCondition();
                    }
                    else {
                        break;
                    }
                    lock.unlock();
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:【Java学习】多线程入门|创建线程|实现线程同步

          本文链接:https://www.haomeiwen.com/subject/ycursctx.html