多线程(上)

一、多线程

1.定义

进程

    进程是程序执行的一条路径

    一个系统级的应用执行就是一个进程

线程

    线程在进程的内部, 一个进程中可以有多个线程

    多个线程并发执行可以提高程序的效率, 可以同时完成多项工作

    多线程节约的是执行程序的等待时间,如果程序排列紧密, 没有等待时间, 多线程下并不能真正的提高效率

2.多线程的应用

    迅雷下载多个任务

    服务器同时处理多个请求

3.多线程并发和并行的区别

    并行就是两个任务同时运行,就是甲任务运行的同时,乙任务也在进行(需要多核CPU)

    并发是指两个任务都请求运行,而处理器只能接受一个任务, 就把这两个任务安排轮流进行, 由于时间间隔较短,使人感觉两个任务都在运行

    比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行

    如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊这就叫并发

4.Java虚拟机运行流程

java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法

JVM启动后至少会创建垃圾回收线程和主线程

5.思考

四核CPU和双核四线程的CPU哪个效率更高?

    四核

二. JAVA中多线程的实现方式一

1.步骤

    继承Thread方式

    定义类继承Thread

    重写run方法

    把新线程要做的事写在run方法中

    创建线程对象

    调用start()方法开启新线程, 内部会自动执行run方法

2.演示

public class MyThread extends Thread{

  @Override-

    public void run() {

         while(true){

              System.out.println("我是子线程");

         }

    }

}

public static void main(String[] args) {

  MyThread myThread = new MyThread();

  myThread.start();

}

3.原理解析

    创建多线程肯定要跟系统平台的底层打交道, 我们程序猿根本就不知道如何去做, 所有,我们仅仅是提供运行代码,至于如何创建多线程全靠java来实现

    继承Thread的形式,每个Thread的子类对象只能创建一个线程

4.测试题

同时启动多个线程打印不同的语句

三. JAVA中多线程的实现方式二

1.步骤

    定义类实现Runnable接口

    实现run方法

    把新线程要做的事写在run方法中

    创建自定义的Runnable的子类对象

    创建Thread对象, 传入Runnable

    调用start()开启新线程, 内部会自动调用Runnable的run()方法

2.演示

public class MyRunnable implements Runnable{

    @Override

    public void run() {

        while(true){

            System.out.println("我是子线程");

        }

    }

}

public static void main(String[] args) {

    MyRunnable myRunnable = new MyRunnable();

    Thread thread = new Thread(myRunnable);

    Thread thread2 = new Thread(myRunnable);

    thread.start();

    thread2.start();

}

3.原理

Thread类中定义了一个Runnable类型的成员变量target用来接收Runnable的子类对象

当调用Thread对象的start()方法的时候, 方法内首先会判断target是否为null. 如果不为null就调用Runnable子类对象的run方法

多个Thread对象可以共享一个Runnable子类对象

4.测试题

在MyThread和MyRunnable中定义一个int型成员变量num, 用这个变量作为循环的控制条件

自定义打印语句, 在主线程中使用这两种方式分别创建2个线程, 并开启多线程

自定义变量num的值, 使两种创建多线程的方式分别打印10条语句


四. 多线程两种方式的区别

1.查看源码的区别:

继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法

实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法

2.继承Thread

好处是:可以直接使用Thread类中的方法,代码简单

弊端是:如果已经有了父类,就不能用这种方法

3.实现Runnable接口

好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的, 多个线程可以非常方便的使用同一个对象的成员变量

弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂

4.思考

当我们调用start()方法之后, 是否就意味着线程立即运行呢 ?

CPU的执行权(时间片): 如果当前线程被cpu 执行的时候, 我们称当前线程获取了cpu的执行权

调用start方法之后,意味着当前线程准备好被执行了


五. 匿名内部类实现多线程的两种方式

1.继承Thread类

public static void main(String[]args) {

    new Thread(){

        public void run(){

            while(true){

                System.out.println("我是子线程");

            }

        }

    }.start();

}

2.实现Runnable接口

public static void main(String[] args) {

    //创建Thread对象,提供Runnable匿名内部类对象

    new Thread(new Runnable(){

        public void run(){

            while(true){

                System.out.println("我是子线程");

            }

        }

    }).start();

}


六. 多线程中获取名字和设置名字

1.获取名字

通过getName()方法获取线程对象的名字

此方法只适用于Thread的形式

Runnable的形式必须通过获取线程对象来获取名字

2.演示

public class MyThread extends Thread{

    @Override

    public void run() {

        System.out.println(this.getName());

    }

}

3.设置名

通过构造方法传入String类型的名字

public static void main(String[]args) {

    new Thread("线程一"){

        public void run(){

            System.out.println(this.getName());

        }

    }.start();

}

通过setName方法可以设置线程对象的名字

public static void main(String[]args) {

    Thread thread1 = new Thread("线程一"){

        public void run(){

            System.out.println(this.getName());

        }

    };

    thread1.setName("线程一");

    thread1.start();

}


七. 获取当前线程的对象

1.定义

我们可以在多线程运行代码中获取代表当前执行线程的对象,并对其进行操作

2.演示

通过获取对象的形式在Runnable运行代码中查看当前线程的名称

public static void main(String[] args) {

    new Thread(new Runnable() {

        @Override

        public void run() {

          //获取线程的名称

            System.out.println(Thread.currentThread().getName());

        }

    }).start();

}

通过获取线程对象的形式设置线程的名称

public static void main(String[] args) {

    new Thread(new Runnable() {

        @Override

        public void run() {

            Thread.currentThread().setName("线程二");//设置线程的名称

            System.out.println(Thread.currentThread().getName());

        }

    }).start();

}

通过Thread的构造方法设置线程的名称

public static void main(String[] args) {

    new Thread(new Runnable() {

        @Override

        public void run() {

            System.out.println(Thread.currentThread().getName());

        }

    //通过构造方法设置线程的名称

    },"线程二").start();

}


八. 休眠线程

1.定义

在线程的执行代码中调用Thread.sleep()方法,就可以将线程休眠若干毫秒

休眠结束的线程进入可运行状态而不是直接运行

2.演示

public static void main(String[] args) {

    new Thread(new Runnable() {

        @Override

        public void run() {

            long time1 = System.currentTimeMillis();

            System.out.println(time1);

            try{

                Thread.sleep(10000);//设置线程等待10000毫秒

            }catch(InterruptedException e) {

                e.printStackTrace();

            }

            long time2 = System.currentTimeMillis();

            System.out.println(time2);

        }

    //通过构造方法设置线程的名称

    }).start();

}

3.测试题(龟兔赛跑)

需求: 乌龟和兔子赛跑总赛程100m, 兔子的速度是10m/s, 乌龟的速度是5m/s.乌龟和兔子都是每跑完10米输出一次结果, 当兔子跑到70米的时候休息2s ,编程模拟比赛过程


九. 守护线程

1.定义

围绕着其他非守护线程运行, 该线程不会单独运行,当其他非守护线程都执行结束后,自动退出

调用Thread的setDaemon()方法设置一个线程为守护线程

2.应用场景

使用飞秋聊天时, 我们打开了多个聊天窗口, 当我们关闭主程序时,其他所有的聊天窗口都会随之关闭

3.演示

public static void main(String[] args) {


    Thread t1 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<5;i++) {

                System.out.println("线程一运行中");

            }

        }

    });


    Thread t2 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("守护线程运行..........");

            }

        }

    });

    t2.setDaemon(true);

    t1.start();

    t2.start();

}


十. 加入线程

1.定义

当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续

2.常用方法

join()  优先执行指定线程

join(毫秒)  优先执行指定线程若干毫秒

3.演示

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程一运行中");

                try{

                    Thread.sleep(10);

                }catch(InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    });

    Thread t2 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程二运行中..........");

                try{

                    t1.join();//让线程一先执行

                    t1.join(100);//让线程一先执行100毫秒

                }catch(InterruptedException e) {

                    e.printStackTrace();

                }

                try{

                    Thread.sleep(10);

                }catch(InterruptedExceptione) {

                    e.printStackTrace();

                }

            }

        }

    });

    //只有调用了加入线程的线程才会停止运行,其他线程不受影响

    Thread t3 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程三运行中..........");

                try{

                    Thread.sleep(10);

                }catch(InterruptedExceptione) {

                    e.printStackTrace();

                }

            }

        }

    });

    t1.start();

    t2.start();

    t3.start();

}

4.注意事项

只有调用其他线程加入方法的线程才会停止运行,其他线程不受影响


十一. 礼让线程(了解)

1.定义

让出当前线程的执行权

基本看不到效果

2.演示

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程一运行中");

            }

        }

    });

    Thread t2 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程二运行中..........");

                //让出当前线程的执行权

                Thread.yield();

            }

        }

    });​

    t1.start();

    t2.start();

}


十二. 设置线程的优先级

1.定义

每个线程都有优先级 默认是5 , 范围是1-10 ,1表示优先级最低

优先级高的线程在争夺cpu的执行权上有一定的优势,但不是绝对的

2.演示

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程一运行中");

            }

        }

    });

    Thread t2 = new Thread(new Runnable() {

        @Override

        public void run() {

            for(inti=0;i<50;i++) {

                System.out.println("线程二运行中..........");

            }

        }

    });

    t1.setPriority(1);

    t2.setPriority(10);//线程二的优先级高

    t1.start();

    t2.start();

}


十三. 多线程安全问题

1.定义

多个线程操作同一个数据时, 因为线程执行的随机性, 就有可能出现线程安全问题

使用同步可以解决这个问题, 把操作数据的代码进行同步,同一时间只能有一个线程只操作数据就可以保证数据的安全性

2.线程安全问题演示

public static void main(String[] args) {

    Thread t1 = new Thread(){

        public void run(){

            while(true){

                if(num>0) {

                    System.out.println(getName()+":"+num--);

                }else{

                    break;

                }

            }

        }

    };

    Thread t2 = new Thread(){

        public void run(){

            while(true){

                if(num>0) {

                    System.out.println(getName()+":"+num--);

                }else{

                    break;

                }

            }

        }

    };

    t1.start();

    t2.start();

}

3.解决安全问题

使用sychronized关键字锁定部分代码

public static void main(String[] args) {

    Thread t1 = new Thread(){

        public void run(){

            while(true){

                //加上锁

                synchronized(Class.class) {

                    if(num>0) {

                        System.out.println(getName()+":"+num--);

                    }else{

                        break;

                    }

                }​

            }

        }

    };

    Thread t2 = new Thread(){

        public void run(){

            while(true){

                //加上统一把锁

                synchronized(Class.class) {

                    if(num>0) {

                        System.out.println(getName()+":"+num--);

                    }else{

                        break;

                    }

                }

            }

        }

    };

    t1.start();

    t2.start();

}

4.注意事项

使用同一把锁的代码才能实现同步

没有获取到锁的线程即使得到了cpu的执行权,也不能运行

尽量减少锁的范围,避免效率低下

锁可以加在任意类的代码中或方法上

5.测试题

火车站总共有100张票, 四个窗口同时卖票, 当票卖光了之后,提示"没票了...",编程模拟场景


十四. 死锁

1.定义

使用同步的多个线程同时持有对方运行时所需要的资源

多线程同步时, 多个同步代码块嵌套,很容易就会出现死锁

锁的嵌套越多,越容易造成死锁的情况

2.演示

线程一需要先获取左边的筷子然后获取右边的筷子才能吃饭

线程二需要先获取右边的筷子然后获取左边的筷子才能吃饭

当线程一持有左边的筷子,线程二持有右边的筷子时,相互等待对方释放锁,但又同时持有对方释放锁的条件,互不相让,就会造成无限等待

private static String s1 = "筷子左";

private static String s2 = "筷子右";

public static void main(String[] args) {

    new Thread() {

        public void run() {

            while(true) {

                synchronized(s1) {

                    System.out.println(getName()+"...拿到"+s1+"等待"+s2);

                    synchronized(s2) {

                        System.out.println(getName()+"...拿到"+s2+"开吃");

                    }

                }

            }

        }

    }.start();

    new Thread() {

        public void run() {

            while(true) {

                synchronized(s2) {

                    System.out.println(getName()+"...拿到"+s2+"等待"+s1);

                    synchronized(s1) {

                        System.out.println(getName()+"...拿到"+s1+"开吃");

                    }

                }

            }

        }

    }.start();

}


总结:

1.几个基本概念

进程  线程  并行 并发

2.多线程

目的 : 认识多线程, 让多个程序可以同时执行

3.多线程的实现方式

Thread  Runnable

4.线程的状态(重点)

新建  --> 就绪 --> 运行  --> 休眠 --> 消亡

线程状态之间的转换

5.几个小方法

休眠  守护  加入  礼让  优先级

线程有两种 , 非守护线程和守护线程

守护线程的特性 : 在所有非守护线程结束之后, 守护线程也会理解结束

6.线程安全问题

产生的原因 : 多个线程共享资源

解决办法: 1. 不要使用共享资源  2. 加锁

加锁的缺点 : 降低效率  尽量不要给会造成等待的代码加锁

7.死锁

使用同步的多个线程同时持有对方运行时所需要的资源

避免办法: 尽量不要使用所的嵌套, 嵌套的越多, 死锁的概率越大

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359