Skip to content

18.多线程

  • 进程和线程的区

    • 每个进程都有独立的代码和数据空间,进程的切换会有很大的开销
    • 同一类线程共享代码和数据空间,每个线程有独立运行的栈和程序计数器,线程切换的开销小
  • 多进程和多线程

    • 多进程:在操作系统中能同时运行多个任务(程序)
    • 多线程:在同一应用程序中有多个顺序流同时执行
  • 多线程实现的两种方式

    • 继承Thread类 —— java.lang.Thread
    • 实现Runnable接口 —— java.lang.Runnable
  • run( )方法 — 线程运行体

  • 要将一段代码(线程体)在一个新的线程上运行,该代码应该在一个线程类的run( )函数中

    • 写一个类implements Runnable接口,且必须覆盖Runnable接口中的run( )方法
    • 写一个类extends Thread类,且必须重写Thread类的run( )方法

18.1 线程状态及其生命周期

一个 Thread 对象在它的整个生存期中能以几种不同的状态存在

start( ) — 方法使线程处于可以运行的状态,但 不一定意味着该线程立即开始运行

包含等待的线程状态

18.2 继承Thread类实现线程类

  • 定义线程类继承Thread类
  • 覆盖run( )方法 public void run( )
  • run( ) 与 start( )
java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       MyGc
 * 创建时间:  2020/11/24  9:05
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class MyGc extends  Thread {


    @Override
    public void run() {
       while (true){
           System.out.println("Gc ...  "+System.currentTimeMillis());
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       App
 * 创建时间:  2020/11/24  9:07
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class App {
    public static void main(String[] args) {

//        new MyGc().run();  错误的启动方式

        new MyGc().start(); //启动线程

        while (true){
            System.out.println("Main  ...  "+System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



    }
}

18.3 实现Runnable接口实现线程类

  • 定义线程类实现Runnable接口
  • 线程共享同样 的数据和代码
  • 覆盖Runnable接口中的唯一的方法 public void run( )
  • 使用Runnable接口可以避免由于JAVA的单继承性带来的局限
  • 适合多个相同的程序代码的线程去处理同一资源情况,把线程同程序的代码、数据有效的分离 推荐使用实现Runnable接口
java
package com.neuedu.thread;

/**
 * 项目:java02
 * 创建时间:  2021/11/24   16:31
 * 作者 :jshand
 * 描述 :
 */
public class Program extends Object implements Runnable {
    int day = 1;
    int sleep = 1000;
    private String name;
    public Program(String threadName,int sleep) {
        this.name = threadName;
        this.sleep = sleep;
    }


    //多线程是执行的 调度函数
    @Override
    public void run() {
        while (true){
            System.out.println(name+"工作 "+(day++));
            //休眠1000毫秒
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


package com.neuedu.thread;

/**
 * 项目:java02
 * 创建时间:  2021/11/24   16:24
 * 作者 :jshand
 * 描述 :
 */
public class Runner {


    public static void main(String[] args) {


//        Work w1 = new Work("Java开发人员" , 1000);
//        Work w2 = new Work("Python开发人员",3000);
//
//        //启动线程
//        w1.start();
//        w2.start();




        Program p1 = new Program("Java开发人员" , 1000);
        Program p2 = new Program("Python开发人员",3000);

        //启动线程
        new Thread(p1).start();
        new Thread(p2).start();

    }
}

18.4 线程类中的主要方法

  • 线程的优先级
  • 线程的休眠
java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       MyTicket
 * 创建时间:  2020/11/24  9:24
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class MyTicket implements  Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            System.out.println("Ticket ...  "+System.currentTimeMillis()+"\tpre\t"+Thread.currentThread().getPriority());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       App
 * 创建时间:  2020/11/24  9:07
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class App {
    public static void main(String[] args) {

//        new MyGc().run();

//        new MyGc().start(); //启动线程

        Thread t1 = new Thread(new MyTicket());
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (true){
            System.out.println("监督员  ...  "+System.currentTimeMillis()+"\tt1isAlive\t"+t1.isAlive());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
  • 线程的中断

    java
    package com.neusoft.thread;
    
    /**
     * 项目:      javaadvice
     * 类名:       MyTicket
     * 创建时间:  2020/11/24  9:24
     * 描述 :
     * 作者 :     张金山
     * QQ :     314649444
     * Site:      https://jshand.gitee.io
     */
    public class MyTicket implements  Runnable {
        private boolean isStop = false;
        @Override
        public void run() {
           while(true){
                System.out.println("Ticket ...  "+System.currentTimeMillis()+"\tpre\t"+Thread.currentThread().getPriority());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if(isStop) {
                    break;
                }
            }
        }
    
        public boolean isStop() {
            return isStop;
        }
    
        public void setStop(boolean stop) {
            isStop = stop;
        }
    }
    java
    package com.neusoft.thread;
    
    /**
     * 项目:      javaadvice
     * 类名:       App
     * 创建时间:  2020/11/24  9:07
     * 描述 :
     * 作者 :     张金山
     * QQ :     314649444
     * Site:      https://jshand.gitee.io
     */
    public class App {
        public static void main(String[] args) {
    
            MyTicket ticket = new MyTicket();
            Thread t1 = new Thread(ticket);
            t1.setPriority(Thread.MIN_PRIORITY);
            t1.start();
    
            for (int i = 0;; i++) {
    
                System.out.println("监督员  ...  "+System.currentTimeMillis()+"\tt1isAlive\t"+t1.isAlive());
                try{
                    if(i==5 ){
    //                    t1.stop();
    //                    t1.interrupt();
                        //设置状态,通过状态修改程序的执行星狂
                        ticket.setStop(true);
    
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
    }

18.5 线程同步

  • 有时两个或多个线程可能会试图同时访问一个资源
    • 例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据 在此情况下,数据可能会变得不一致
  • 为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”
    • 当一个线程运行到需要同步的语句后,CPU不去执行其他线程中的、可能影响当前线程中的下一句代码的执行结果的代码块,必须等到下一句执行完后才能去执行其他线程中的相关代码块,这就是线程同步

18.5.1 锁

  • 对象的锁标志
    • 每个对象都有一个锁标志 使用synchronized可与锁标志交互
  • synchronized关键字的用法有两种:
    • synchronized语句
    • synchronized方法
  • synchronized语句: synchronized (obj)

一旦一个包含同步方法(用synchronized修饰)的线程被CPU调用,其他线程就无法调用相同对象的同步方法。当一个线程在一个同步方法内部,所有试图调用该方法的同实例的其他线程必须等待

18.5.2 代码实现

java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       SellTicket
 * 创建时间:  2020/11/24  10:28
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class SellTicket implements Runnable {
    private String name = "";
    public static int num = 50;
    
    
    private Object obj = new Object();

    public SellTicket(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (num>0) {
            System.out.println(name+"在卖票\t "+getTicket());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //方法级别的同步锁
//    private synchronized  int getTicket(){
//        return num--;
//    }

    //对象级别的锁
    private  int getTicket(){
        
        
        synchronized (obj){
            return num--;
        }
    }

    public static void main(String[] args) {


        new Thread(new SellTicket("王小花")).start();
        new Thread(new SellTicket("牛晓磊")).start();
    }

}

注意事项:

受到synchronized保护的程序代码块和方法中,要访问的对象属性必须设定为private,因为如果不设定为private,那么就可以用不同的方式来访问它,这样就达不到保护的效果了

18.5.3 实现同步的两种方式—两种方式的优缺点

synchronized方法

  • 优点:
    • 可以显示的知道哪些方法是
    • 被synchronized关键字保护的
  • 缺点:
  • 方法中有些内容是不需要同步的,如果该方法执行会花很长时间,那么其他人就要花较多时间等待锁被归还;
  • 只能取得自己对象的锁,有时候程序设计的需求,可能会需要取得其他对象的锁;

synchronized代码块

  • 优点:
    • 可以针对某段程序代码同步,不需要浪费时间在别的程序代码上;
    • 可以取得不同对象的锁;
  • 缺点:
  • 无法显示的得知哪些方法
  • 是被synchronized关键字保护的;

18.5.4 死锁

两个线程,彼此在等待对方占据的锁

锁的归还几种方式:

  • 基本上执行完同步的程序代码后,锁就会自动归还;
  • 用break语句跳出同步的语句块,不过这对于写在方法声明的synchronized没有作用;
  • 遇到return语句;
  • 遇到了异常;

18.6 练习

1、利用Thread实现,要求多线程求解某范围素数每个线程负责1000范围:线程1找1-1000;线程 2 找 1001-2000;线程 3 找2001-3000。编程程序将每个线程找到的素数及时打印。 [必做题]

2、利用Runnable实现,要求多线程求解某范围素数每个线程负责1000范围:线程1找1-1000;线程 2 找 1001-2000;线程 3 找2001-3000。编程程序将每个线程找到的素数及时打印。 [必做题]

3、编写一个Java程序(包括一个主程序类,一个线程类。在主程序类中创建2个线程,将其中一个线程的优先级设为10,另一个线程的优先级设为6。让优先级为10的线程打印200次“线程1正在运行”,优先级为6的线程打印200次“线程2正在运行”。 [选做题]

4、编写一个计时器,每隔一秒钟,在控制台打印出最新时间。 [必做题]

素数

java
package com.neusoft.thread;

/**
 * 项目:      javaadvice
 * 类名:       Sushu
 * 创建时间:  2020/11/24  11:44
 * 描述 :   素数
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class Sushu extends Thread {

    int start = 0;
    int end = 0;

    public Sushu(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        for (int i = start; i <=end; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean is = true;
            for (int j = 2; j <i ; j++) {
                if( i %j == 0){
                    is = false;
                    break;
                }
            }
            if(is){
                System.out.println("素数\t"+i);
            }
        }
    }

    public static void main(String[] args) {
        new Sushu(1,1000).start();
        new Sushu(1001,2000).start();
        new Sushu(2001,3000).start();


    }
}

线程200次

java
package com.neusoft.thread;


import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 项目:      javaadvice
 * 类名:       TimeThread
 * 创建时间:  2020/11/24  11:46
 * 描述 :
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 */
public class TimeThread implements Runnable {

    String name ;

    public TimeThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(name+ "正在运行");
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {
        Thread t1 = new Thread(new TimeThread("线程1"));
        t1.setPriority(Thread.MAX_PRIORITY);

        Thread t2 = new Thread(new TimeThread("线程2"));
        t2.setPriority(6);

        t1.start();
        t2.start();

        
    }
}

计时器

java
    public static void main(String[] args) {


        new Thread(()->{
            SimpleDateFormat simpleDateFormat  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            while(true){
                Date now = new Date();
                String time = simpleDateFormat.format(now);
                System.out.println(time);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

Released under the MIT License.