Java入门3.1---多线程

一.程序、进程、线程的概念

1.基本概念

  1. 程序program:为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  2. 进程process:程序的一次执行过程,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
    1. 如:运行中的QQ,运行中的MP3播放器
    2. 程序是静态的,进程的动态的。
  3. 线程thread:线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
    1. 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

2.进程与多线程

2.1 区别

  1. 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
  2. 开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
  3. 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
  4. 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
  5. 包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

2.2 线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程

  1. 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  2. 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  3. Java垃圾回收就是一个典型的守护线程。
  4. 若JVM中都是守护线程,当前JVM将退出。

(1)守护线程Daemon Thread(又称内核线程)

 (2)用户线程(ULT)

  有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为后台线程(Daemon Thread),又称为守护线程或精灵线程。JVM的垃圾回收线程就是典型的后台线程。后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

  系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理系统上,多线程在多处理器上并行运行。线程的创建、调度和管理由内核完成,效率比ULT要慢,比进程操作快。

  用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞。

举例:

调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

 举例:WPS,QQ等应用
package 多线程;

public class DaemonThread extends Thread {
    // 定义后台线程的线程执行体与普通线程没有任何区别
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(getName() + "" + i);
        }
    }

    public static void main(String[] args) {
        DaemonThread t = new DaemonThread();
        // 将此线程设置成后台线程
        t.setDaemon(true);
        // 启动后台线程
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "" + i);
        }
        // -----程序执行到此处,前台线程(main线程)结束------
        // 后台线程也应该随之结束
    }
}

结果:

main0
Thread-00
main1
Thread-01
main2
Thread-02
main3
Thread-03
main4
Thread-04
main5
Thread-05
main6
Thread-06
main7
main8
main9
Thread-07
Thread-08
Thread-09
Thread-010
Thread-011
Thread-012
Thread-013
Thread-014
Thread-015
Thread-016
Thread-017
Thread-018
Thread-019

  上面程序中的t线程设置成后台线程,然后启动该线程,本来该线程应该执行到i等于999时才会结束,但运行程序时不难发现该后台线程无法运行到999,因为当主线程也就是程序中唯一的前台线程运行结束后,JVM会主动退出,因而后台线程也就被结束了。Thread类还提供了一个isDaemon0方法,用于判断指定线程是否为后台线程。

  从上面程序可以看出,主线程默认是前台线程, t线程默认也是前台线程。并不是所有的线程默认都是前台线程,有些线程默认就是后台线程——前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

  前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到做出响应,需要一定时间。而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)必须在start()方法之前调用,否则会引发IllegaIThreadStateException异常。

 

2.3 为什么需要设计线程?

  计算机操作系统里面有两个重要概念:并发和隔离。

  1. 并发是为了尽量让硬件利用率高,线程是为了在系统层面做到并发。线程上下文切换效率比进程上下文切换会高很多,这样可以提高并发效率。
  2. 隔离也是并发之后要解决的重要问题,计算机的资源一般是共享的,隔离要能保障崩溃了这些资源能够被回收,不影响其他代码的使用。所以说一个操作系统只有线程没有进程也是可以的,只是这样的系统会经常崩溃而已,操作系统刚开始发展的时候和这种情形很像。

  所以,线程和并发有关系,进程和隔离有关系。线程基本是为了代码并发执行引入的概念,因为要分配cpu时间片,暂停后再恢复要能够继续和没暂停一样继续执行;进程相当于一堆线程加上线程执行过程中申请的资源,一旦挂了,这些资源都要能回收,不影响其他程序。

2.4 何时需要多线程?

  1. 程序需要同时执行两个或多个任务。
  2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  3. 需要一些后台运行的程序时。 

二.Java中多线程的创建和使用

1.继承Thread类与实现Runnable接口

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。

Thread类的特性:

  1. 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
  2. 通过该Thread对象的start()方法来调用这个线程。

1.1 继承Thread类

1.2 实现Runnable接口

package 多线程;
/**
 * 创建一个子线程,完成1-100之间自然数的输出,同样,让主线程执行同样的操作
 * 方法1:继承java.lang.Thread类
 */
//第一步:创建继承Thread的子类
class SubThread extends java.lang.Thread{
    //第二步:重写Thread类的run()方法,方法内实现此子线程要完成的功能
    public void run(){
        for(int i=1;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        //第三步:创建子类的对象
        SubThread st1 = new SubThread();
        SubThread st2 = new SubThread();
        //第四步:调用线程的start(),启动此线程,调用相应的run()方法
        st1.start();
        st2.start();
        //一个线程只能执行一次start()
//        st.run(); //不能通过Thread实现类对象的run()去启动一个线程
        for(int i=1;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
package 多线程;
//创建多线程的方式2:通过实现的方式
//1.创建一个实现了Runnable接口的类
class PrintNum1 implements Runnable{
    //2.实现接口的方法
    public void run(){
        for(int i=1;i<=100;i++){
            if(i%2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class TestThread3 {
    public static void main(String[] args) {
        //3.创建一个Runnable接口实现类的对象
        PrintNum1 p = new PrintNum1();
//        p.start();
//        p.run();
        //要想启动一个多线程,必须调用start方法()
        //4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
        Thread t1 = new Thread(p);
        //5.调用start()方法,启动线程并执行run()
        t1.start();//启动线程,执行Thread对象生成时构造器形参的对象的run()方法。

        //再创建一个线程
        Thread t2 = new Thread(p);
        t2.start();
    }
} 
C:\Users\n00568290\IdeaProjects\java_learning\out\production\java_learning 多线程.TestThread
main:1
Thread-0:1
Thread-1:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
main:2
main:3
main:4
main:5
main:6
Thread-0:17
Thread-1:2
Thread-0:18
Thread-0:19
Thread-0:20
Thread-0:21
Thread-0:22
Thread-0:23
Thread-0:24
Thread-0:25
Thread-0:26
Thread-0:27
Thread-0:28
main:7
Thread-0:29
Thread-1:3
Thread-0:30
main:8
Thread-0:31
Thread-1:4
Thread-0:32
main:9
Thread-0:33
Thread-1:5
Thread-0:34
main:10
Thread-0:35
Thread-1:6
Thread-0:36
main:11
Thread-0:37
Thread-1:7
Thread-0:38
main:12
Thread-0:39
Thread-1:8
Thread-0:40
main:13
Thread-0:41
Thread-1:9
Thread-0:42
main:14
Thread-0:43
Thread-1:10
Thread-0:44
main:15
Thread-0:45
Thread-1:11
Thread-0:46
main:16
Thread-0:47
Thread-1:12
Thread-0:48
main:17
Thread-0:49
Thread-0:50
Thread-1:13
Thread-0:51
main:18
Thread-0:52
Thread-1:14
Thread-0:53
Thread-0:54
Thread-0:55
Thread-0:56
Thread-0:57
Thread-0:58
main:19
Thread-0:59
Thread-0:60
main:20
Thread-1:15
main:21
Thread-0:61
main:22
Thread-0:62
Thread-0:63
Thread-1:16
Thread-0:64
Thread-1:17
Thread-0:65
Thread-0:66
Thread-0:67
Thread-0:68
Thread-0:69
main:23
Thread-1:18
main:24
Thread-0:70
main:25
Thread-1:19
main:26
main:27
main:28
main:29
Thread-0:71
main:30
Thread-1:20
main:31
Thread-0:72
main:32
Thread-1:21
main:33
Thread-0:73
main:34
Thread-1:22
main:35
Thread-0:74
main:36
Thread-1:23
main:37
Thread-0:75
main:38
Thread-1:24
main:39
Thread-0:76
main:40
Thread-1:25
main:41
Thread-0:77
main:42
Thread-1:26
main:43
Thread-0:78
main:44
Thread-1:27
main:45
Thread-0:79
main:46
Thread-1:28
main:47
main:48
main:49
Thread-0:80
main:50
Thread-1:29
main:51
Thread-0:81
main:52
Thread-1:30
main:53
Thread-0:82
main:54
Thread-1:31
Thread-1:32
main:55
Thread-1:33
Thread-0:83
Thread-1:34
main:56
Thread-1:35
Thread-0:84
Thread-1:36
main:57
main:58
main:59
main:60
Thread-0:85
Thread-0:86
Thread-0:87
Thread-0:88
Thread-0:89
Thread-0:90
main:61
Thread-1:37
main:62
main:63
main:64
Thread-0:91
Thread-0:92
main:65
main:66
main:67
main:68
Thread-1:38
main:69
Thread-0:93
main:70
Thread-1:39
main:71
Thread-0:94
Thread-0:95
main:72
Thread-1:40
main:73
Thread-0:96
main:74
Thread-1:41
main:75
Thread-0:97
main:76
Thread-1:42
main:77
Thread-0:98
main:78
Thread-1:43
main:79
Thread-0:99
main:80
Thread-1:44
main:81
Thread-0:100
main:82
Thread-1:45
main:83
Thread-1:46
main:84
Thread-1:47
Thread-1:48
Thread-1:49
Thread-1:50
main:85
Thread-1:51
main:86
Thread-1:52
main:87
Thread-1:53
main:88
Thread-1:54
main:89
Thread-1:55
main:90
Thread-1:56
Thread-1:57
Thread-1:58
Thread-1:59
Thread-1:60
main:91
Thread-1:61
main:92
Thread-1:62
main:93
Thread-1:63
Thread-1:64
main:94
Thread-1:65
main:95
Thread-1:66
main:96
Thread-1:67
main:97
Thread-1:68
main:98
Thread-1:69
main:99
Thread-1:70
main:100
Thread-1:71
Thread-1:72
Thread-1:73
Thread-1:74
Thread-1:75
Thread-1:76
Thread-1:77
Thread-1:78
Thread-1:79
Thread-1:80
Thread-1:81
Thread-1:82
Thread-1:83
Thread-1:84
Thread-1:85
Thread-1:86
Thread-1:87
Thread-1:88
Thread-1:89
Thread-1:90
Thread-1:91
Thread-1:92
Thread-1:93
Thread-1:94
Thread-1:95
Thread-1:96
Thread-1:97
Thread-1:98
Thread-1:99
Thread-1:100

Process finished with exit code 0
"C:\Program Files\Java\jdk-14.0.1\bin\java.exe" "-javaagent:D:\software\IntelliJ IDEA\IntelliJ IDEA 2020.1\lib\idea_rt.jar=55831:D:\software\IntelliJ IDEA\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\n00568290\IdeaProjects\java_learning\out\production\java_learning 多线程.TestThread3
Thread-0:2
Thread-0:4
Thread-1:2
Thread-0:6
Thread-1:4
Thread-0:8
Thread-1:6
Thread-0:10
Thread-1:8
Thread-0:12
Thread-1:10
Thread-0:14
Thread-1:12
Thread-0:16
Thread-1:14
Thread-1:16
Thread-0:18
Thread-1:18
Thread-0:20
Thread-1:20
Thread-0:22
Thread-1:22
Thread-0:24
Thread-1:24
Thread-0:26
Thread-1:26
Thread-0:28
Thread-1:28
Thread-0:30
Thread-1:30
Thread-0:32
Thread-1:32
Thread-0:34
Thread-1:34
Thread-0:36
Thread-0:38
Thread-1:36
Thread-1:38
Thread-1:40
Thread-0:40
Thread-1:42
Thread-0:42
Thread-1:44
Thread-0:44
Thread-1:46
Thread-0:46
Thread-1:48
Thread-0:48
Thread-1:50
Thread-0:50
Thread-1:52
Thread-0:52
Thread-1:54
Thread-0:54
Thread-1:56
Thread-0:56
Thread-1:58
Thread-0:58
Thread-1:60
Thread-0:60
Thread-1:62
Thread-0:62
Thread-1:64
Thread-0:64
Thread-1:66
Thread-0:66
Thread-1:68
Thread-0:68
Thread-1:70
Thread-0:70
Thread-1:72
Thread-0:72
Thread-1:74
Thread-1:76
Thread-0:74
Thread-0:76
Thread-1:78
Thread-0:78
Thread-1:80
Thread-0:80
Thread-1:82
Thread-0:82
Thread-1:84
Thread-0:84
Thread-1:86
Thread-0:86
Thread-1:88
Thread-0:88
Thread-1:90
Thread-0:90
Thread-1:92
Thread-0:92
Thread-1:94
Thread-0:94
Thread-1:96
Thread-0:96
Thread-1:98
Thread-0:98
Thread-1:100
Thread-0:100

Process finished with exit code 0
注意:启动线程使用start()方法,而不是run()方法。永远不要调用线程对象的run()方法。调用start0方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直按调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法并发执行。也就是说,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。需要指出的是,调用了线程的run()方法之后,该线程已经不再处于新建状态,不要再次调用线程对象的start()方法。只能对处于新建状态的线程调用start()方法,否则将引发IllegaIThreadStateExccption异常。

 

继承的方式 VS 实现的方式:

(1)联系:public class Thread inplements Runnable

(2)哪个方式好?实现的方式优于继承的方式

  1. 实现的方式避免了java单继承的局限性;
  2. 如果多个线程要操作同一份资源(或数据),更适合使用实现的方式。同时,共享数据所在的类可以作为Runnable接口的实现类。

使用多线程的好处:

(1)提高应用程序的响应,对图形化界面更有意义,可增强用户体验。

(2)提高计算机系统CPU的利用率

(3)改善程序结构,将既长又复杂的进程分为多个线程,独立进行,利于理解和修改。

2.Thread类的主要方法

package 多线程;
/**
 * 创建一个子线程,完成1-100之间自然数的输出,同样,让主线程执行同样的操作
 * 方法1:继承java.lang.Thread类
 * start():启动线程并执行相应的run()方法
 * run():子线程要执行的代码放入run()方法中
 * currentThread():静态的,调取当前的线程
 * getName():获取此线程的名字
 * setName():设置此线程的名字
 * yield():调用此方法的线程释放当前CPU的执行权
 * join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行。
 * isalive():判断当前线程是否还活着
 * sleep(long l):显示的让当前线程睡眠l毫秒
 * 线程通信:wait() notify() notifyall()
 */
//第一步:创建继承Thread的子类
class SubThread extends java.lang.Thread{
    //第二步:重写Thread类的run()方法,方法内实现此子线程要完成的功能
    public void run(){
        for(int i=1;i<=100;i++){
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        //第三步:创建子类的对象
        SubThread st1 = new SubThread();
        st1.setName("子线程1");
        SubThread st2 = new SubThread();
        st2.setName("子线程2");
        //第四步:调用线程的start(),启动此线程,调用相应的run()方法
        st1.start();
        st2.start();
        //一个线程只能执行一次start()
//        st.run(); //不能通过Thread实现类对象的run()去启动一个线程
        Thread.currentThread().setName("=======主线程");
        for(int i=1;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
//            if (i % 10 == 0){
//                Thread.currentThread().yield();
//            }
            if(i == 20){
                try{
                    st1.join();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
        System.out.println(st1.isAlive());
    }
}
=======主线程:1
=======主线程:2
=======主线程:3
=======主线程:4
=======主线程:5
=======主线程:6
=======主线程:7
=======主线程:8
=======主线程:9
=======主线程:10
=======主线程:11
=======主线程:12
=======主线程:13
=======主线程:14
=======主线程:15
=======主线程:16
=======主线程:17
=======主线程:18
=======主线程:19
=======主线程:20
子线程1:1
子线程2:1
子线程2:2
子线程1:2
子线程1:3
子线程2:3
子线程2:4
子线程1:4
子线程1:5
子线程2:5
子线程2:6
子线程1:6
子线程1:7
子线程2:7
子线程1:8
子线程2:8
子线程2:9
子线程1:9
子线程1:10
子线程2:10
子线程1:11
子线程2:11
子线程1:12
子线程2:12
子线程1:13
子线程2:13
子线程1:14
子线程2:14
子线程1:15
子线程2:15
子线程1:16
子线程2:16
子线程1:17
子线程2:17
子线程1:18
子线程2:18
子线程1:19
子线程2:19
子线程1:20
子线程2:20
子线程1:21
子线程2:21
子线程1:22
子线程2:22
子线程1:23
子线程2:23
子线程1:24
子线程2:24
子线程1:25
子线程2:25
子线程1:26
子线程2:26
子线程1:27
子线程2:27
子线程1:28
子线程2:28
子线程1:29
子线程2:29
子线程1:30
子线程2:30
子线程1:31
子线程2:31
子线程1:32
子线程2:32
子线程1:33
子线程2:33
子线程1:34
子线程2:34
子线程1:35
子线程2:35
子线程1:36
子线程2:36
子线程1:37
子线程2:37
子线程1:38
子线程2:38
子线程1:39
子线程2:39
子线程1:40
子线程2:40
子线程1:41
子线程2:41
子线程1:42
子线程2:42
子线程1:43
子线程2:43
子线程1:44
子线程2:44
子线程1:45
子线程2:45
子线程1:46
子线程2:46
子线程1:47
子线程2:47
子线程1:48
子线程2:48
子线程1:49
子线程2:49
子线程1:50
子线程2:50
子线程1:51
子线程2:51
子线程1:52
子线程2:52
子线程1:53
子线程2:53
子线程1:54
子线程2:54
子线程1:55
子线程2:55
子线程1:56
子线程2:56
子线程1:57
子线程2:57
子线程1:58
子线程2:58
子线程1:59
子线程2:59
子线程1:60
子线程2:60
子线程1:61
子线程2:61
子线程1:62
子线程2:62
子线程1:63
子线程2:63
子线程1:64
子线程2:64
子线程1:65
子线程2:65
子线程1:66
子线程2:66
子线程1:67
子线程2:67
子线程1:68
子线程2:68
子线程1:69
子线程2:69
子线程1:70
子线程2:70
子线程1:71
子线程2:71
子线程1:72
子线程2:72
子线程1:73
子线程2:73
子线程1:74
子线程2:74
子线程1:75
子线程2:75
子线程1:76
子线程2:76
子线程1:77
子线程2:77
子线程1:78
子线程2:78
子线程1:79
子线程2:79
子线程1:80
子线程2:80
子线程1:81
子线程2:81
子线程1:82
子线程2:82
子线程1:83
子线程2:83
子线程1:84
子线程2:84
子线程1:85
子线程2:85
子线程1:86
子线程2:86
子线程1:87
子线程2:87
子线程1:88
子线程2:88
子线程1:89
子线程2:89
子线程1:90
子线程2:90
子线程1:91
子线程2:91
子线程1:92
子线程2:92
子线程1:93
子线程2:93
子线程1:94
子线程2:94
子线程1:95
子线程2:95
子线程1:96
子线程2:96
子线程1:97
子线程2:97
子线程1:98
子线程2:98
子线程1:99
子线程2:99
子线程1:100
=======主线程:21
=======主线程:22
=======主线程:23
=======主线程:24
=======主线程:25
=======主线程:26
=======主线程:27
=======主线程:28
=======主线程:29
=======主线程:30
=======主线程:31
=======主线程:32
=======主线程:33
=======主线程:34
=======主线程:35
=======主线程:36
=======主线程:37
=======主线程:38
=======主线程:39
=======主线程:40
=======主线程:41
=======主线程:42
=======主线程:43
=======主线程:44
=======主线程:45
=======主线程:46
=======主线程:47
=======主线程:48
=======主线程:49
=======主线程:50
=======主线程:51
=======主线程:52
=======主线程:53
=======主线程:54
=======主线程:55
=======主线程:56
=======主线程:57
=======主线程:58
=======主线程:59
=======主线程:60
=======主线程:61
=======主线程:62
=======主线程:63
=======主线程:64
=======主线程:65
=======主线程:66
=======主线程:67
=======主线程:68
=======主线程:69
=======主线程:70
=======主线程:71
=======主线程:72
=======主线程:73
=======主线程:74
=======主线程:75
=======主线程:76
=======主线程:77
=======主线程:78
=======主线程:79
=======主线程:80
=======主线程:81
=======主线程:82
=======主线程:83
=======主线程:84
=======主线程:85
=======主线程:86
=======主线程:87
=======主线程:88
=======主线程:89
=======主线程:90
=======主线程:91
=======主线程:92
=======主线程:93
=======主线程:94
=======主线程:95
=======主线程:96
=======主线程:97
=======主线程:98
=======主线程:99
=======主线程:100
false
子线程2:100


2.1 start():启动线程并执行相应的run()方法

2.2 run():子线程要执行的代码放入run()方法中

2.3 currentThread():静态的,调取当前的线程

2.4 getName():获取此线程的名字

2.5 setName():设置此线程的名字

2.6 yield():调用此方法的线程释放当前CPU的执行权。

  yield()方法是一个和sleep()方法有点相似的方法,它也是Threard类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。

  实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。下面程序使用yield()方法来让当前正在执行的线程暂停。

package 多线程;

public class YieldThread extends Thread {
    public YieldThread(String name) {
        super(name);
    }

    // 定义run方法作为线程执行体
    public void run() {
        for (int i = 0; i < 30; i++) {
            System.out.println(getName() + "" + i);
            // 当i等于20时,使用yield方法让当前线程让步
            if (i == 2) {
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // 启动两条并发线程
        YieldThread yt1 = new YieldThread("高级");
        // 将ty1线程设置成最高优先级
         yt1.setPriority(Thread.MAX_PRIORITY);
        yt1.start();
        YieldThread yt2 = new YieldThread("低级");
        // 将yt2线程设置成最低优先级
         yt2.setPriority(Thread.MIN_PRIORITY);
        yt2.start();
    }
}
高级0
高级1
高级2
低级0
高级3
低级1
高级4
低级2
高级5
低级3
高级6
低级4
高级7
低级5
高级8
低级6
高级9
低级7
高级10
低级8
高级11
低级9
高级12
低级10
高级13
低级11
高级14
低级12
高级15
低级13
高级16
低级14
高级17
低级15
高级18
低级16
高级19
低级17
高级20
低级18
高级21
低级19
高级22
低级20
高级23
低级21
高级24
低级22
高级25
低级23
高级26
低级24
高级27
低级25
高级28
低级26
高级29
低级27
低级28
低级29

sleep()方法和yield()方法的区别如下:

  1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会
  2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态:而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行
  3. sleep()方法声明抛出了InterruptcdException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常
  4. sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

举例1:以下会使线程进入到阻塞状态的是 (ABD)

A. wait

B. sleep

C. yield

D. join

举例2:以下哪些在接收到Thread.interrupt()会抛出InterruptedException异常 (ACD)

A. Thread.sleep()

B. java.net.Socket的阻塞方法

C. java.nio.channels.SocketChannel的阻塞方法

D. Thread.wait()

解析:在编写需要中止的多线程程序时,必须选用能够响应interrupt的标准库或第三方库。Java标准库中的会阻塞的方法(如Thread.sleep()或者SocketChannel.write())一般会在interrupt之后抛出InterruptedException。但有某些方法则不理会interrupt,如Socket.write(),必须回避这些方法。

不好的例子:java.net.Socket类的方法阻塞时不响应interrupt!写多线程程序时必须回避这些类。

2.7 join()

当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

举例:

package 多线程;

public class JoinThread extends Thread {
    // 提供一个有参数的构造器,用于设置该线程的名字
    public JoinThread(String name) {
        super(name);
    }

    // 重写run方法,定义线程执行体
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "" + i);
        }
    }

    public static void main(String[] args) throws Exception {
        // 启动子线程
        new JoinThread("新线程").start();
        JoinThread jt2 = new JoinThread("线程nxf");
        jt2.start();
        for (int i = 0; i < 100; i++) {
            if (i == 20) {
                JoinThread jt = new JoinThread("被Join的线程");
                jt.start();
                // main线程调用了jt线程的join()方法,main线程
                // 必须等jt执行结束才会向下执行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName() + "" + i);
        }
    }
}
View Code

 

  

上面程序中一共有4个线程,主方法开始时就启动了名为"新线程"的子线程,该子线程将会和main线程并发执行。当主线程的循环变量i等于20时启动了名为"被Join的线程"的线程,该线程不会和main线程并发执行。main线程必须等该线程执行结束后才可以向下执行。在名为"被Join的线程"的线程执行时,实际上只有2个子线程并发执行,而主线程处于等待状态。运行上面程序。从上面的运行结果可以看出,主线程执行到i=20时启动,并join了名为"被Join的线程"的线程,所以主线程将一直处于阻塞状态,直到名为"被Join的线程"的线程执行完成。

2.8 isalive()

  判断当前线程是否还活着。

  1. 当线程处于就绪、运行、阻塞状态时,该方法将返回true;
  2. 当线程处于新建、死亡状态时,该方法将返回false。

2.9 sleep(long l)

  如果需要让当前正在执行的线程暂停一段时,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

举例:

package 多线程;
import java.util.Date;

class ThreadTest extends Thread {
    // 定义后台线程的线程执行体与普通线程没有任何区别

    public ThreadTest(String name) {
        super(name);
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "" + i);
        }
    }
}

public class SleepThread {
    public static void main(String[] args) throws Exception {
        ThreadTest threadXf = new ThreadTest("nxf");
        threadXf.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("当前时间: " + new Date());
            // 调用sleep方法让当前线程暂停1s。
            Thread.sleep(1000);
        }
    }
}
nxf0
nxf1
nxf2
nxf3
nxf4
nxf5
nxf6
nxf7
nxf8
nxf9
当前时间: Wed Jun 03 11:31:10 CST 2020
当前时间: Wed Jun 03 11:31:11 CST 2020
当前时间: Wed Jun 03 11:31:12 CST 2020
当前时间: Wed Jun 03 11:31:13 CST 2020
当前时间: Wed Jun 03 11:31:14 CST 2020
当前时间: Wed Jun 03 11:31:15 CST 2020
当前时间: Wed Jun 03 11:31:16 CST 2020
当前时间: Wed Jun 03 11:31:17 CST 2020
当前时间: Wed Jun 03 11:31:18 CST 2020
当前时间: Wed Jun 03 11:31:19 CST 2020
下面程序调用sleep()方法来暂停主线程的执行,因为该程序有2个主线程,当主线程进入睡眠后,系统执行另外一个线程,并且该进程在1ms的时间内执行完毕,主线程依次输出10条字符串,输出2条字符串之间的时间间隔为1秒。

2.10 线程通信

  1. wait()
  2. notify()
  3. notifyall()

3.线程的调度与设置优先级

3.1 调度策略

  1. 时间片
  2. 抢占式:高优先级的线程抢占CPU

3.2 Java的调度方法

  1. 同优先级线程组成先进先出队列(先到先服务),使用时间片策略。
  2. 对高优先级,使用优先调度的抢占式策略。 

3.3 线程的优先级

线程的优先级控制(设置优先级表示该线程抢到CPU的概率增大,不保证一定等到该线程执行结束才去执行其他线程。)

  1. MAX_PRIORITY(10);最大是10
  2. MIN_PRIORITY(1);最小是1
  3. NORM_PRIORITY(5); 默认是5

涉及的方法:

  1. getPriority():返回线程优先级
  2. setPriority(int newPriority):改变线程的优先级
    1. SubThread st1 = new SubThread(); 
      st1.setName("子线程1"); 
      st1.setPriority(Thread.MAX_PRIORITY);
  3. 线程创建时继承父线程的优先级

三.线程的生命周期

JDK中用Thread.State枚举表示了线程的几种状态。

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

  1. 新建:当一个Thread类及其子类的对象被声明并创建时,新生的线程对象处于新建状态;
  2. 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件;
  3. 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
  4. 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
  5. 死亡: 线程完成了它的全部工作或线程被提前强制性地中止。

1.新建

  当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

2.就绪

  当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

3.运行--->阻塞

  当发生如下情况时,线程将会进入阻塞状态:

  1. 线程调用sleep()方法主动放弃所占用的处理器资源
  2. 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
  3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将存更深入的介绍
  4. 线程在等待某个通知(notify)
  5. 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

  当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是说,被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。

4.阻塞--->就绪

  针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态

  1. 调用sleep()方法的线程经过了指定时间。
  2. 线程调用的阻塞式IO方法已经返回
  3. 线程成功地获得了试图取得的同步监视器
  4. 线程正在等待某个通知时,其他线程发出了个通知
  5. 处于挂起状态的线程被调甩了resume()恢复方法。

  线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定。当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源时,该线程进入就绪状态。但有一个方法例外,调用yield()方法可以让运行状态的线程转入就绪状态。

5.死亡

线程会以如下3种方式结束,结束后就处于死亡状态

  1. run()或call()方法执行完成,线程正常结束。
  2. 线程抛出一个未捕获的Exception或Error
  3. 直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。

不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,该线程将不可再次作为线程执行。

四.线程的同步

1.线程安全问题存在的原因?

由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题。

2.如何来解决线程的安全问题? 

必须让一个线程操作共享数据完毕之后,其他线程才有机会参与共享数据的操作。

3.java如何实现线程的安全:线程的同步机制? 

3.1 方式一:同步代码块

synchronized(同步监视器){   

  //需要被同步的代码块(即操作共享数据的代码) 

1.共享数据:多个线程需要共同操作的数据(变量) 。明确哪部分是操作共享数据的代码。

2.同步监视器():任何一个类的对象充都可以充当锁。哪个线程获取此监视器,谁就执行大括号里被同步的代码。要想保证线程的安全,必须要求所有的线程共用同一把锁。

3.使用实现Runnable接口的方式创建多线程时,同步代码块中的锁,可以使用this。如果使用继承Thread的方式,慎用this(保证是唯一的一个对象才可以使用this)。

举例:

package 多线程;
//使用实现Runnable接口的方式,售票

class Window2 implements Runnable {
    int ticket = 100;
 // Object obj = new Object();//任何一个类的对象都可以充当锁

    public void run() {
      Object obj = new Object();
        while(true) {
//          synchronized (obj){//在本问题中,this表示w,所以obj可以用this替代
            synchronized (this){
                if (ticket > 0){
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
                }
            }
        }
    }
}
public class TestWindow2{
    public static void main(String[] args) {
        Window w = new Window(); // 三个线程共用一个对象
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window extends Thread{
    static int ticket = 100; //公用静态变量
    static Object obj = new Object();
    public void run(){
        while (true){
            synchronized(obj){ //因为有三个对象,这里不能用this
                if (ticket > 0){
                    try{
                        Thread.currentThread().sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
                }
            }
        }
    }

public class TestWindow{
    public static void main(String[] args) {
        Window w1 = new Window(); //3个对象
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

3.2 方式二:同步方法

将操作共享数据的方法声明为synchronized,比如:public synchronized void show(){//操作共享数据的代码}。

注:

  1. 对于非静态的方法而言,使用同步的话,默认的锁为:this。如果使用继承的方式实现多线程的话,慎用!
  2. 对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。Class clazz = Singleton.class

即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程在外等待,直至此线程执行完此方法。

class Window4 implements Runnable{
    int ticket = 100;
    public void run(){
        while(true){
            show();
        }
    }
    public synchronized void show() {
        if (ticket > 0){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
        }
        }
}

public class SynchronizedThread2{
    public static void main(String[] args) {
        Window4 w = new Window4(); // 三个线程共用一个对象
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
采用继承的方式时,如果有多个对象,不能使用同步方法,因为会产生多把锁。

  

View Code
 

3.3 举例:实现多人在银行存钱的功能

功能需求:银行有一个账户,若两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

  1. 是否涉及到多线程?是,有两个储户
  2. 是否有共享数据?有,同一个账户
  3. 得考虑线程的同步。(两种方法处理线程的安全)

拓展问题:可否实现两个储户交替存钱的操作(需要使用线程通信)

package 多线程;
/**
 * 银行有一个账户,若两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
 1.是否涉及到多线程?是,有两个储户
 2.是否有共享数据?有,同一个账户
 3.得考虑线程的同步。(两种方法处理线程的安全)

 拓展问题:可否实现两个储户交替存钱的操作(需要使用线程通信)
 */
class Account{
    double balance = 0; //余额
    public Account(){
    }
    //存钱
    public synchronized void deposit(double amt){ //存钱,共用一个account,是可以用同步方法的
        balance += amt;
        try{
            Thread.currentThread().sleep(10);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+balance);
    }
}

class Customer extends Thread{
    Account account;
    public Customer(Account account){
        this.account = account;
    }
    public void run(){
        for(int i=0;i<3;i++){
            account.deposit(1000);
        }
    }
}

public class BankThread{
    public static void main(String[] args) {
        Account acct = new Account();
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}
class Account{
    double balance = 0; //余额
    public Account(){
        System.out.println("账户余额"+balance);
    }
    public Account(int balance){
        this.balance = balance;
        System.out.println("账户余额"+this.balance);
    }
    //存钱
    public synchronized void deposit(double amt){ //存钱,共用一个account,是可以用同步方法的
        balance += amt;
        try{
            Thread.currentThread().sleep(10);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+balance);
    }
}

class Customer implements Runnable{
    private Account account = null;
//    public Customer(){
//    }
    public Customer(Account account){
        this.account = account;
    }
//    public void setCustomer(Account account){
//        this.account = account;
//    }
//    public Account getCustomer(){
//        return account;
//    }
    public void run(){
        for(int i=0;i<3;i++){
            account.deposit(1000);
        }
    }
}

public class BankThread{
    public static void main(String[] args) {
        Account acct = new Account();
        Customer customer = new Customer(acct);
        Thread c1 = new Thread(customer);
        Thread c2 = new Thread(customer);
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}
甲:1000.0
甲:2000.0
甲:3000.0
乙:4000.0
乙:5000.0
乙:6000.0 
 

3.4 死锁

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。死锁是我们在使用同步时,需要避免的问题!(处理线程同步时容易出现。)

package 多线程;
//死锁的问题:处理线程同步时容易出现
//不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
public class DeadLock {
    static StringBuffer sb1 = new StringBuffer();
    static StringBuffer sb2 = new StringBuffer();

    public static void main(String[] args) {
        new Thread(){
            public void run(){
                synchronized (sb1){
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("a");
                    synchronized (sb2){
                        sb2.append("b");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
        new Thread(){
            public void run(){
                synchronized (sb2){
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("c");
                    synchronized (sb1){
                        sb2.append("d");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
    }
} 

五.线程的通信

1.wait()、notify()、notifyAll()

如下三个方法必须使用在同步代码块或同步方法中!

  1. wait():当在同步中,执行此方法,则此线程“等待”,直至其他线程执行notify()的方法,将其唤醒,唤醒后继续其wait()后的代码执行。令当前线程挂起并放弃CPU、同步资源、使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。
  2. notify()/notifyAll():在同步中,执行到此方法,则唤醒某一个被wait的线程(优先级最高者)/所有的被wait的线程

Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.illegalMonitorStateException异常。

  1. 释放锁:wait()
  2. 不释放锁:sleep()、yield()、suspend(过时,可能导致死锁)

举例1:使用两个线程打印1-100,线程1和线程2交替打印。

package 多线程;
//使用两个线程打印1-100,线程1和线程2交替打印。
//如下的三个关键字使用的话,都得在同步代码块或同步方法中。
/**
 * 1.wait():一旦一个线程执行到wait(),就释放当前的锁。
 令当前线程挂起并放弃CPU、同步资源、使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。
 * 2.notify():唤醒wait的一个或多个线程
 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
 * 3.notifyAll():唤醒正在排队等待资源的所有线程结束等待。
 * Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,
 * 否则会报java.lang.illegalMonitorStateException异常。
 */

class PrintThread implements Runnable{
    int num = 1;
    public void run(){
        while(true) {
            synchronized (this) {
                notify();//一拿到锁就唤醒
                if (num <= 100) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + num);
                    num += 1;
                } else {
                    break;
                }
                try {
                    wait();//执行完一次就释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

public class TestCommunication {
    public static void main(String[] args) {
        PrintThread pt = new PrintThread();
        Thread t1 = new Thread(pt);
        Thread t2 = new Thread(pt);
//        Thread t3 = new Thread(pt);
        t1.setName("甲");
        t2.setName("乙");
//        t3.setName("丙");
        t1.start();
        t2.start();
//        t3.start();
    }
}
甲1
乙2
甲3
乙4
甲5
乙6
甲7
乙8
甲9
乙10
甲11
乙12
甲13
乙14
甲15
乙16
甲17
乙18
甲19
乙20
甲21
乙22
甲23
乙24
甲25
乙26
甲27
乙28
甲29
乙30
甲31
乙32
甲33
乙34
甲35
乙36
甲37
乙38
甲39
乙40
甲41
乙42
甲43
乙44
甲45
乙46
甲47
乙48
甲49
乙50
甲51
乙52
甲53
乙54
甲55
乙56
甲57
乙58
甲59
乙60
甲61
乙62
甲63
乙64
甲65
乙66
甲67
乙68
甲69
乙70
甲71
乙72
甲73
乙74
甲75
乙76
甲77
乙78
甲79
乙80
甲81
乙82
甲83
乙84
甲85
乙86
甲87
乙88
甲89
乙90
甲91
乙92
甲93
乙94
甲95
乙96
甲97
乙98
甲99
乙100

举例2:生产者消费者

package 多线程;
/**
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下。
 * 如果店中有空位放产品了,再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下。
 * 如果店中有空位了再通知消费者来取走产品。
 *
 * 分析:
 * 1.是否涉及到多线程的问题?是,生产者、消费者
 * 2.是否涉及共享数据?有,考虑线程的安全
 * 3.此共享数据是谁?即为产品的数量
 * 4.是否涉及到线程的通信?存在生产者与消费者的通信
 */
class Clerk{ //店员
    int product;
    public synchronized void addProduct() throws InterruptedException { //生产者
        if (product >= 20){
            wait();
        }else {
            product += 1;
            System.out.println(Thread.currentThread().getName()+":生产力第"+product);
            notifyAll();
        }
    }
    public synchronized void eatProduct() throws InterruptedException { //消费者
        if(product <= 0){
            wait();
        }else{
            System.out.println(Thread.currentThread().getName()+":消费了第"+product);
            product -= 1;
            notifyAll();
        }
    }
}

class Consumer implements Runnable{ //消费者
    Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        while (true){ //有产品
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                clerk.eatProduct();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable { //生产者
    Clerk clerk;
    public Producer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run() {
        System.out.println("生产者开始生产产品");
        while (true){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                clerk.addProduct();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ConsumerProducer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Consumer c1 = new Consumer(clerk);
        Thread t1 = new Thread(c1);//一个生产者的线程

        Producer p1 = new Producer(clerk);
        Thread t2 = new Thread(p1);//一个消费者的线程

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

  

生产者开始生产产品
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-0:消费了第1
Thread-1:生产力第1
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
Thread-0:消费了第2
Thread-1:生产力第2
...

  

更多内容见Java入门3.3---线程通信 - nxf_rabbit75 - 博客园

六、线程阻塞的原因

导致线程阻塞的原因主要由以下几方面。

1、线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n毫秒,然后恢复运行。

2、线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才能恢复运行。

3、线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒。

4、线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到了用户的输入数据才从read()方法返回。进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态。

5、请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回。

6、线程从Socket的输入流读取数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现了异常,才从输入流的read()方法返回或异常中断。输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型。

int read(); 只要输入流中有一个字节,就算足够。

int read(byte[] buff); 只要输入流中的字节数目与参数buff数组的长度相同,就算足够。

String readLine(); 只要输入流中邮一行字符串,就算足够。值得注意的是,InputStream类并没有readLine方法,在过滤流BufferedReader类中才有此方法。

7、线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断。

8、调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close方法时,会进入阻塞状态,直到底层Socket发送完所有剩余数据,或者超过了setSoLinger()方法设置的延迟时间,才从close()方法返回。

在服务器程序中,线程在以下情况下可能会进入阻塞状态。

1、线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才能accept()方法返回。

2、线程从Socket的输入流读入数据时,如果输入流没有足够数据,就会进入阻塞状态。

3、线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断。

 

参考文献:

【1】尚硅谷Java视频_深入浅出、兼顾实战的Java基础视频(课堂实录)

【2】每个程序员都会遇到的面试问题:谈谈进程和线程的区别 - 知乎

【3】Java 多线程 并发编程_escaflone的专栏-CSDN博客_java

【4】Java并发结构 | 并发编程网 – ifeve.com

【5】线程阻塞的原因_道阻且长,行则将至-CSDN博客_线程阻塞的原因

 

posted @ 2020-06-02 15:30  nxf_rabbit75  阅读(279)  评论(0编辑  收藏  举报