程序与进程

程序:一段静态的代码,是一个普通的文件。

进程:程序一次动态执行过程,程序被加载到内存条中进行执行,最后释放,加载/执行/释放也是进程的生命周期。

任务:进程也可以被称为任务,基于操作系统运行的。

支持多任务的系统被称为多任务系统(Windows)。

多线程与进程

一个进程中也可以有多个任务一起执行,比如QQ,视频的同时也可以语音,打字,还可以发表情。每个任务都被称为一个线程。

多线程奥义:

并发:任务同时发生,同时进行,被称为并发(并行),也就是多线程。

线程不能独立纯在,必须依赖于进程,在进程中运行。

每一个进程至少有一个线程,被称为主线程。

程序被分为

多线程:不只一个线程。

单线程:只有一个线程。

生活例子:

看电视的同时嗑瓜子,吃苹果,每件事都是一个任务/线程。

线程与CPU

电脑有

双核四线程:CPU有两个核心,同一时间可以处理四个线程

四核八线程:CPU有4个核心,同一时间可以处理8个线程

为什么系统里的任务(线程)有很多(比如20个线程),而四核八线程的CPU可以处理20个线程呢?

CPU每回最多处理8个线程,CPU会为每个线程分配一个时间片,且在单位时间内每个线程只能执行一小段,就这样循环切换线程执行,因此我们就看到流程的操作系统。

线程可以设置优先级,同一时间优先级高的优先被执行。

线程优缺点

优点

提高软件界面的响应熟读

提高后台程序加载速度

更好的利用多线程CPU资源,提高效率。

缺点

如果线程过多,系统需要花费大量时间处理线程切换,因此降低了CPU的处理速度,CPU支持的线程数越多,多线程越有优势。

因此要合理利用多线程,不要乱用。

线程随机性:同一时间,哪些线程被执行你是不知道的,如果我们想要控制线程的执行顺序,需要自己编写代码。

创建一个无用的Thread

Java多线程代码编写有三种方式

0.直接new Thread,没有任何意义

1.继承Thread,重写run方法

2.实现Runnable,new Thread();

3.Timer和TimerTask

启动一个线程后,里面默认空的,start之后,立刻就执行完成。

1610803597820.png

继承Thread,重写run方法

Java程序中默认会有一个main线程

同一个线程不可以启动多次,否则会抛异常

同一个线程类可以创建多条线程分支

只有当程序中所有线程都结束之后,程序才会结束

多线程示例图

1610803597857.png

创建线程程序示例

  1. public class TVThread extends Thread{
  2. @Override
  3. public void run() {
  4. for(int i = 1; i < 50 ;i++) {
  5. System.out.println(this.getName() + "---看一眼电视 " + i);
  6. try {
  7. //等待0.1s
  8. Thread.sleep(100);
  9. } catch (InterruptedException e) {
  10. // TODO Auto-generated catch block
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. }
  16. public class EatThread extends Thread{
  17. public void run() {
  18. for(int i = 0; i<30;i++) {
  19. System.out.println(this.getName() + "====吃一口苹果 "+ i);
  20. try {
  21. //间隔0.2s吃一口苹果
  22. Thread.sleep(200);
  23. } catch (InterruptedException e) {
  24. // TODO Auto-generated catch block
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. public class ThreadTest {
  31. public static void main(String[] args) throws InterruptedException { //main线程
  32. //创建线程
  33. TVThread a = new TVThread();
  34. a.setName("a线程");
  35. EatThread b = new EatThread();
  36. b.setName("b线程");
  37. EatThread c = new EatThread();
  38. c.setName("c线程");
  39. //启动线程
  40. a.start();
  41. b.start();
  42. System.out.println("a和b线程已经启动");
  43. //间隔2s后启动c线程
  44. Thread.sleep(2000);
  45. c.start();
  46. System.out.println("---------------c线程已经启动");
  47. System.out.println("main线程结束");
  48. }//main线程结束
  49. }

运行结果:

1610803597892.png

线程的生命周期

线程的生命周期

1.诞生(new Thread)

当创建一个Thread实例的时候,未启动状态

2.就绪

当start之后,线程就处于就绪状态,时刻准备着被cpu执行

3.运行

线程得到了cpu资源进行运算,但是只有一小段时间片,时间到了之后,cpu就切换到其他线程,当前线程又变为就绪状态。

4.死亡

当run执行结束,或者中途被其他线程杀死,

自然终止:正常运行run()方法结束后终止

异常终止:调用stop()等方法杀死

5.堵塞/阻塞

睡眠:sleep(long ms);

等待:wait

队列:join

sleep方法

一旦调用了sleep方法,该线程就由运行状态进入了阻塞状态。

利用sleep方法对线程的控制是非常不精确的。

1610803597937.png

join方法

join方法可以精确控制线程

join方法也会导致线程阻塞

特点:如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成

join方法程序示例

  1. public class ThreadTest {
  2. public static void main(String[] args) throws InterruptedException { //main线程
  3. //创建线程
  4. TVThread a = new TVThread();
  5. a.setName("a线程");
  6. EatThread b = new EatThread();
  7. b.setName("b线程");
  8. //启动线程
  9. a.start();
  10. b.start();
  11. System.out.println("a和b线程已经启动");
  12. //在main线程中写一个join,代表main线程结束后,才会接着执行main线程
  13. a.join();
  14. System.out.println("main线程结束");
  15. }//main线程结束
  16. }

程序运行结果

1610803597968.png

多线程同步问题

当多个线程同时访问同一个对象时就可能发生不同步问题,不同步问题发生的原因是因为运算流程被破坏。

有个账户类,里面有属性余额

有两个人,同时拥有同一个账户,A人取钱,B人存钱,这两个人同时在操作同一个账户,这就涉及到一个线程同步的问题。

多线程并发问题程序示例

  1. public class Account {
  2. //账户余额
  3. private int n;
  4. public int getN() {
  5. return n;
  6. }
  7. public void setN(int n) {
  8. this.n = n;
  9. }
  10. }
  11. public class AccTest {
  12. public static void main(String[] args) {
  13. Account a = new Account();
  14. a.setN(100);
  15. //匿名内部类
  16. new Thread("张三") { //连续存款50次,每次100
  17. @Override
  18. public void run() {
  19. for(int i = 0;i < 50;i++) {
  20. try {
  21. //获取账户金额
  22. int n = a.getN();
  23. n += 100;
  24. //延时1ms
  25. sleep(1);
  26. a.setN(n);
  27. System.out.println(this.getName() + "存入100元后的余额: " + a.getN());
  28. } catch (Exception e) {
  29. // TODO: handle exception
  30. }
  31. }
  32. }
  33. }.start();
  34. new Thread("小明") {//连续取款存款50次,每次100
  35. @Override
  36. public void run() {
  37. for(int i = 0;i < 50;i++) {
  38. try {
  39. //获取账户金额
  40. int n = a.getN();
  41. n -= 100;
  42. //延时1ms
  43. sleep(1);
  44. a.setN(n);
  45. System.out.println(this.getName() + "取出存入100元后的余额: " + a.getN());
  46. } catch (Exception e) {
  47. // TODO: handle exception
  48. }
  49. }
  50. }
  51. }.start();
  52. }
  53. }

程序运行的结果

1610803598074.png

显然结果是不对的,因为没有进行线程同步,请看下一节线程同步。

结果不对的最终原因:

当李四存钱时,取出账户余额100,然后存钱100+100 = 200,存完的钱还没有存入到内存中,这时小明开始存钱,取出账户余额100,然后取钱100-100=0,小明这个线程比李四线程执行的快一些(有可能是网络延时问题/CPU执行线程的随机性),导致小明取出后的钱被优先存入到内存中,这时呢?李四线程执行完,把200元存入到内存中,此时账户的余额是200元,这肯定不对。

synchronized线程同步

账户问题的解决方案:

当一个用户操作账户时,就应该将当前用户锁死,其他用户不可以操作该账户,也就是排队问题。

synchronized:线程同步,让线程排队,解决不同步问题

锁:队伍中的信物,使用不同的锁就有不同的队伍

可以把Account,Data,Object等等类的对象当做锁。

当一个用户操作账户类时,我用账户作为锁,如果这时另一个进程识别到这个锁时,就不会执行锁里的程序块。

只有当锁里的程序块被执行完时,才把锁打开,其它线程可以重新使用这个锁,并且执行锁里的程序块。

程序示例

  1. public class Account {
  2. //账户余额
  3. private int n;
  4. public int getN() {
  5. return n;
  6. }
  7. public void setN(int n) {
  8. this.n = n;
  9. }
  10. }
  11. public class AccTest {
  12. public static void main(String[] args) {
  13. Account a = new Account();
  14. a.setN(100);
  15. //匿名内部类
  16. new Thread("张三") { //连续存款50次,每次100
  17. @Override
  18. public void run() {
  19. for(int i = 0;i < 50;i++) {
  20. try {
  21. synchronized(a) {
  22. //获取账户金额
  23. int n = a.getN();
  24. n += 100;
  25. //延时1ms
  26. sleep(1);
  27. a.setN(n);
  28. System.out.println(this.getName() + "存入100元后的余额: " + a.getN());
  29. }
  30. System.out.println(this.getName() + "-----OVER------"+a.getN());
  31. } catch (Exception e) {
  32. // TODO: handle exception
  33. }
  34. }
  35. }
  36. }.start();
  37. new Thread("小明") {//连续取款存款50次,每次100
  38. @Override
  39. public void run() {
  40. for(int i = 0;i < 50;i++) {
  41. try {
  42. synchronized (a) {
  43. //获取账户金额
  44. int n = a.getN();
  45. n -= 100;
  46. //延时1ms
  47. sleep(1);
  48. a.setN(n);
  49. System.out.println(this.getName() + "取出100元后的余额: " + a.getN());
  50. }
  51. } catch (Exception e) {
  52. // TODO: handle exception
  53. }
  54. System.out.println(this.getName() + "-----OVER------"+a.getN());
  55. }
  56. }
  57. }.start();
  58. }
  59. }

1610803598130.png

核心重点:

1.什么是多线程

2.多线程怎么用

3.线程不同步问题

4.不同步问题的解决方案

创建线程的5种方法

直接new Thread

  1. //创建一个线程对象
  2. Thread t1 = new Thread();
  3. //因为最原始的线程里面没有内容,start之后线程就会立马消失
  4. t1.start();

继承Thread类,重写run方法

这种方法比较常用

  1. public class Eat extends Thread{
  2. @Override
  3. public void run() {
  4. try {
  5. System.out.println("开始吃-----"+this.getName());
  6. for(int i = 1;i < 5;i++) {
  7. System.out.println("吃一口------"+i);
  8. sleep(200);
  9. }
  10. } catch (Exception e) {
  11. // TODO: handle exception
  12. }
  13. System.out.println(this.getName() + "吃完了");
  14. }
  15. }
  16. public class Test {
  17. public static void main(String[] args) {
  18. //创建吃线程
  19. Eat e = new Eat();
  20. //为当前线程起个名称
  21. e.setName("张三");
  22. //启动线程
  23. e.start();
  24. System.out.println("主线程结束了");
  25. }
  26. }

程序运行结果

1610803598153.png

实现Runnable接口

TV类不是一个线程,然而这个TV类中的run方法有可能被需要线程调用。

//Eat不是一个线程,Eat中的run方法可以被多个线程调用
public class Eat extends Object implements Runnable{
    @Override
    public void run() {
        try {
            //获取当前正在执行这段代码的线程
            Thread now = Thread.currentThread();
            String str = now.getName();

            System.out.println("开始吃-----" + str);
            for(int i = 1;i < 5;i++) {

                System.out.println(str + "吃一口------"+i);
                Thread.sleep(200);
            }
            System.out.println(str + "吃完了");
        } catch (Exception e) {
            // TODO: handle exception
        }

    }
}

public class Test {

    public static void main(String[] args) {
        //该类实现了Runnable接口
        Eat e = new Eat();
        //将Runnable接口的实现传入线程中
        Thread t1 = new Thread(e, "小明");
        t1.start();

        Thread t2 = new Thread(e, "小天");
        t2.start();

        System.out.println("主线程结束了");
    }
}

程序运行的结果

1610803598179.png

匿名内部类

匿名内部类,适合局部少量使用

public class Test {

    public static void main(String[] args) {
        //匿名内部类
        new Thread("小李") {
            @Override
            public void run() {    
                try {
                    for(int i = 0;i < 5;i++) {
                        System.out.println(getName() + "看一眼 ----" + i);
                        sleep(200);
                    }

                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();

        System.out.println("主线程结束了");
    }
}

程序运行结果

1610803598214.png

Runnable匿名内部类

public class Test {

    public static void main(String[] args) {
        //Runnable匿名内部类
        new Thread(new Runnable() {
            @Override
            //run可以被不同的线程调用
            public void run() {
                try {
                    for(char i = 'A';i < 'Z';i++) {
                        //获取当前进程
                        Thread now = Thread.currentThread();
                        System.out.println(now.getName() + "看一眼 ----" + i);
                        Thread.sleep(200);
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                }

            }
        }, "张三").start();

        System.out.println("主线程结束了");
    }

}

程序运行结果

1610803598240.png

多线程经典——生产者和消费者

notify与wait

notify解锁线程,wait锁住线程。

使用notify和wait方法必须对线程进行同步,否则会报错。

notify和wait是依赖于同步锁,因此synchronized中的锁,必须填写notify和wait的线程。

接下来说说利用wait()和notify()来实现生产者和消费者并发问题:

显然要保证生产者和消费者并发运行不出乱,主要要解决:当生产者线程的缓存区为满的时候,就应该调用wait()来停止生产者继续生产,而当生产者满的缓冲区被消费者消费掉一块时,则应该调用notify()唤醒生产者,通知他可以继续生产;同样,对于消费者,当消费者线程的缓存区为空的时候,就应该调用wait()停掉消费者线程继续消费,而当生产者又生产了一个时就应该调用notify()来唤醒消费者线程通知他可以继续消费了。

这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。

因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。

wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。

notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。

调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor(锁)的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

1个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

notify和wait的程序演示

public class Test {
    // 在多线程间共享的对象上使用wait
    private static String shareObj = "true";

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        Thread1 a = new Thread1();
        a.setName("a");
        /* 启动线程a */
        a.start();

        Thread2 b =  new Thread2();
        b.setName("b");
        /* 启动线程b */
        b.start();

    } 

    static class Thread1 extends Thread{

        @Override
        public void run() {
            try {
                synchronized(shareObj) {
                    System.out.println("开始等待线程获取锁");
                    /* 等待获取锁,需要其他线程将对象锁进行释放 */
                    shareObj.wait();
                }

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(this.getName() + "线程,获取到锁");
        }
    }

    static class Thread2 extends Thread{
        @Override
        public void run() {
            try {
                synchronized (shareObj) {
                    shareObj.notify();
                    System.out.println("线程"+Thread.currentThread().getName() + "调用shareObj.notify()");

                    /* 只要当前线程等待2s后,线程Thread1才会被执行
                     * 因为只要执行完synchronized语句块,对象锁才会被释放
                     * 当对象锁被释放的时候,线程Thread1才会执行,因为获得到锁
                     */
                    sleep(2000);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
            System.out.println(this.getName() + "释放了锁");
        }
    }
}

生产者与消费者的程序

package demo;

import java.util.Vector;

public class Test {

    /* 鞋店 */
    public static Vector<Xie> shop = new Vector<Xie>();

    public static void main(String[] args) {
        //开始生产
        Producer a = new Producer();
        a.start();

        //开始消费
        Custormer b = new Custormer();
        b.start();
    }

    /**
     * 生产者/工厂
     * 鞋店里最多放10个,工厂生产数量达到30个就解散
     */
    static class Producer extends Thread{
        @Override
        public void run() {

            for(int i = 1;i <= 30;i++) {
                try {
                    /*
                     * 生产一双鞋,同时将这双鞋添加到鞋店里
                     * 为了模拟现实,生产一双鞋是需要时间的,因此
                     * 使用sleep延时1s。
                     */
                    Xie x = new Xie();
                    sleep(1000);
                    shop.add(x);

                    synchronized(shop) {
                        /* 
                         *已经生产一双鞋,消费者可以购买
                         *首先要为商店对象解锁,以便消费者线程可以获取到shop锁
                         *接着执行。
                         */
                        System.out.println("工厂生产一双鞋了");    
                        System.out.println("当前鞋的数量为:" + shop.size());
                        shop.notify();                     

                        /* 
                         * 判断商店的鞋是否大于10双
                         * 如果大于10双,就让该线程停在这块,等待其他线程
                         * 释放shop对象锁。
                         */
                        while(shop.size() >= 10) {
                            System.out.println("鞋店,库存达到10个,停止进货");
                            shop.wait();
                        }
                    }

                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
            System.out.println("工厂生产的鞋的数量达到上限,工厂倒闭了");
        }
    }

    /**
     * 消费者
     * @author 27823
     *
     */
    static class Custormer extends Thread{
        @Override
        public void run() {

            //消费者买50双鞋
            for(int i = 0;i < 50;i++) {
                System.out.println("消费者来了....");
                try {
                    synchronized(shop) {
                        /*
                         *判断鞋店是否有鞋,可供消费者进行购买
                         *如果没有鞋,就使用wait方法使消费者线程处于等待的状态,需要生产者
                         *使用 notify()释放锁,然后消费者就可以获取到锁,即线程可以接着执行。
                         */
                        while(shop.size() <= 0) {
                            System.out.println("仓库空荡荡,消费者在等待着!!!");
                            shop.wait();
                        }

                        /* 消费者买鞋,取走第一双鞋 */
                        shop.removeElementAt(0);;
                        System.out.println("消费者买了一双鞋,商店还剩" + shop.size() + "双鞋");

                        /* 唤醒工厂线程 */
                        shop.notify();
                    }

                    System.out.println("客户:全五星好评,2秒后再买");

                    /*
                     * 这个延时时间是模拟人购买的时间间隔,在这个时间间隔里,工厂可以接着生产鞋
                     * 注意:该sleep必须放在synchronized语句外边,因为notify()方法只是唤醒工厂线程
                     * ,然工厂线程准备执行,但是程序只要没有执行完synchronized语句块,shop对象锁
                     * 就不会被释放,只有出了synchronized语句块,对象锁才会被释放,因此必须将延时的
                     * 时间放在外边,当延时2000ms时,工厂线程在执行中(生产鞋子)。
                     */
                    sleep(2000);

                } catch (Exception e) {
                    // TODO: handle exception
                    e.getStackTrace();
                }
            }
    }
    }
    //鞋
    static class Xie{

    }
}