Java入门3.1---多线程

Java入门3.1---多线程_第1张图片

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

1.基本概念

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

2.进程与多线程

Java入门3.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)

Java入门3.1---多线程_第3张图片

Java入门3.1---多线程_第4张图片

  有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为后台线程(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
Java入门3.1---多线程_第5张图片
注意:启动线程使用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()方法来控制并发线程的执行。

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);
        }
    }
}
  1 线程nxf0
  2 main0
  3 main1
  4 main2
  5 main3
  6 main4
  7 main5
  8 新线程0
  9 新线程1
 10 新线程2
 11 新线程3
 12 新线程4
 13 新线程5
 14 新线程6
 15 新线程7
 16 新线程8
 17 新线程9
 18 新线程10
 19 新线程11
 20 新线程12
 21 新线程13
 22 新线程14
 23 新线程15
 24 新线程16
 25 新线程17
 26 新线程18
 27 新线程19
 28 新线程20
 29 线程nxf1
 30 线程nxf2
 31 main6
 32 main7
 33 main8
 34 main9
 35 main10
 36 main11
 37 main12
 38 main13
 39 main14
 40 main15
 41 main16
 42 线程nxf3
 43 线程nxf4
 44 线程nxf5
 45 线程nxf6
 46 线程nxf7
 47 线程nxf8
 48 main17
 49 main18
 50 main19
 51 新线程21
 52 新线程22
 53 新线程23
 54 新线程24
 55 新线程25
 56 新线程26
 57 新线程27
 58 新线程28
 59 新线程29
 60 新线程30
 61 新线程31
 62 新线程32
 63 新线程33
 64 新线程34
 65 新线程35
 66 新线程36
 67 新线程37
 68 新线程38
 69 新线程39
 70 新线程40
 71 新线程41
 72 新线程42
 73 新线程43
 74 新线程44
 75 新线程45
 76 新线程46
 77 新线程47
 78 新线程48
 79 新线程49
 80 新线程50
 81 新线程51
 82 新线程52
 83 新线程53
 84 新线程54
 85 新线程55
 86 新线程56
 87 新线程57
 88 新线程58
 89 新线程59
 90 新线程60
 91 新线程61
 92 新线程62
 93 新线程63
 94 新线程64
 95 新线程65
 96 新线程66
 97 新线程67
 98 线程nxf9
 99 新线程68
100 线程nxf10
101 新线程69
102 线程nxf11
103 新线程70
104 线程nxf12
105 新线程71
106 线程nxf13
107 新线程72
108 线程nxf14
109 新线程73
110 新线程74
111 新线程75
112 新线程76
113 新线程77
114 新线程78
115 新线程79
116 新线程80
117 新线程81
118 新线程82
119 线程nxf15
120 线程nxf16
121 线程nxf17
122 线程nxf18
123 线程nxf19
124 线程nxf20
125 线程nxf21
126 线程nxf22
127 线程nxf23
128 线程nxf24
129 线程nxf25
130 线程nxf26
131 线程nxf27
132 线程nxf28
133 线程nxf29
134 线程nxf30
135 新线程83
136 线程nxf31
137 被Join的线程0
138 线程nxf32
139 新线程84
140 线程nxf33
141 被Join的线程1
142 线程nxf34
143 新线程85
144 线程nxf35
145 被Join的线程2
146 线程nxf36
147 新线程86
148 线程nxf37
149 被Join的线程3
150 线程nxf38
151 新线程87
152 线程nxf39
153 被Join的线程4
154 线程nxf40
155 新线程88
156 线程nxf41
157 被Join的线程5
158 线程nxf42
159 新线程89
160 线程nxf43
161 被Join的线程6
162 线程nxf44
163 新线程90
164 线程nxf45
165 新线程91
166 新线程92
167 新线程93
168 新线程94
169 新线程95
170 新线程96
171 新线程97
172 新线程98
173 新线程99
174 被Join的线程7
175 被Join的线程8
176 被Join的线程9
177 被Join的线程10
178 线程nxf46
179 线程nxf47
180 线程nxf48
181 线程nxf49
182 线程nxf50
183 被Join的线程11
184 线程nxf51
185 被Join的线程12
186 线程nxf52
187 被Join的线程13
188 线程nxf53
189 被Join的线程14
190 线程nxf54
191 被Join的线程15
192 线程nxf55
193 线程nxf56
194 被Join的线程16
195 线程nxf57
196 被Join的线程17
197 线程nxf58
198 被Join的线程18
199 线程nxf59
200 被Join的线程19
201 线程nxf60
202 被Join的线程20
203 被Join的线程21
204 被Join的线程22
205 被Join的线程23
206 被Join的线程24
207 被Join的线程25
208 被Join的线程26
209 被Join的线程27
210 被Join的线程28
211 被Join的线程29
212 线程nxf61
213 线程nxf62
214 线程nxf63
215 线程nxf64
216 线程nxf65
217 线程nxf66
218 线程nxf67
219 线程nxf68
220 线程nxf69
221 被Join的线程30
222 线程nxf70
223 被Join的线程31
224 线程nxf71
225 被Join的线程32
226 线程nxf72
227 被Join的线程33
228 线程nxf73
229 被Join的线程34
230 线程nxf74
231 被Join的线程35
232 被Join的线程36
233 被Join的线程37
234 被Join的线程38
235 被Join的线程39
236 被Join的线程40
237 被Join的线程41
238 被Join的线程42
239 被Join的线程43
240 被Join的线程44
241 被Join的线程45
242 被Join的线程46
243 线程nxf75
244 线程nxf76
245 线程nxf77
246 被Join的线程47
247 被Join的线程48
248 被Join的线程49
249 被Join的线程50
250 被Join的线程51
251 被Join的线程52
252 被Join的线程53
253 被Join的线程54
254 被Join的线程55
255 被Join的线程56
256 被Join的线程57
257 被Join的线程58
258 被Join的线程59
259 被Join的线程60
260 被Join的线程61
261 被Join的线程62
262 被Join的线程63
263 被Join的线程64
264 被Join的线程65
265 被Join的线程66
266 被Join的线程67
267 被Join的线程68
268 被Join的线程69
269 被Join的线程70
270 被Join的线程71
271 被Join的线程72
272 被Join的线程73
273 被Join的线程74
274 被Join的线程75
275 被Join的线程76
276 被Join的线程77
277 被Join的线程78
278 被Join的线程79
279 被Join的线程80
280 被Join的线程81
281 被Join的线程82
282 被Join的线程83
283 被Join的线程84
284 被Join的线程85
285 被Join的线程86
286 被Join的线程87
287 被Join的线程88
288 被Join的线程89
289 被Join的线程90
290 被Join的线程91
291 被Join的线程92
292 被Join的线程93
293 被Join的线程94
294 被Join的线程95
295 被Join的线程96
296 被Join的线程97
297 被Join的线程98
298 被Join的线程99
299 线程nxf78
300 线程nxf79
301 线程nxf80
302 线程nxf81
303 线程nxf82
304 线程nxf83
305 线程nxf84
306 线程nxf85
307 线程nxf86
308 线程nxf87
309 线程nxf88
310 线程nxf89
311 线程nxf90
312 线程nxf91
313 线程nxf92
314 线程nxf93
315 线程nxf94
316 线程nxf95
317 线程nxf96
318 线程nxf97
319 线程nxf98
320 线程nxf99
321 main20
322 main21
323 main22
324 main23
325 main24
326 main25
327 main26
328 main27
329 main28
330 main29
331 main30
332 main31
333 main32
334 main33
335 main34
336 main35
337 main36
338 main37
339 main38
340 main39
341 main40
342 main41
343 main42
344 main43
345 main44
346 main45
347 main46
348 main47
349 main48
350 main49
351 main50
352 main51
353 main52
354 main53
355 main54
356 main55
357 main56
358 main57
359 main58
360 main59
361 main60
362 main61
363 main62
364 main63
365 main64
366 main65
367 main66
368 main67
369 main68
370 main69
371 main70
372 main71
373 main72
374 main73
375 main74
376 main75
377 main76
378 main77
379 main78
380 main79
381 main80
382 main81
383 main82
384 main83
385 main84
386 main85
387 main86
388 main87
389 main88
390 main89
391 main90
392 main91
393 main92
394 main93
395 main94
396 main95
397 main96
398 main97
399 main98
400 main99
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. 死亡: 线程完成了它的全部工作或线程被提前强制性地中止。

Java入门3.1---多线程_第6张图片

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();
    }
}
采用继承的方式时,如果有多个对象,不能使用同步方法,因为会产生多把锁。

  

  1 窗口1售票,票号为:100
  2 窗口1售票,票号为:99
  3 窗口3售票,票号为:98
  4 窗口3售票,票号为:97
  5 窗口3售票,票号为:96
  6 窗口3售票,票号为:95
  7 窗口3售票,票号为:94
  8 窗口3售票,票号为:93
  9 窗口3售票,票号为:92
 10 窗口3售票,票号为:91
 11 窗口3售票,票号为:90
 12 窗口3售票,票号为:89
 13 窗口3售票,票号为:88
 14 窗口3售票,票号为:87
 15 窗口3售票,票号为:86
 16 窗口3售票,票号为:85
 17 窗口3售票,票号为:84
 18 窗口3售票,票号为:83
 19 窗口3售票,票号为:82
 20 窗口3售票,票号为:81
 21 窗口3售票,票号为:80
 22 窗口3售票,票号为:79
 23 窗口3售票,票号为:78
 24 窗口3售票,票号为:77
 25 窗口3售票,票号为:76
 26 窗口3售票,票号为:75
 27 窗口3售票,票号为:74
 28 窗口3售票,票号为:73
 29 窗口3售票,票号为:72
 30 窗口3售票,票号为:71
 31 窗口3售票,票号为:70
 32 窗口3售票,票号为:69
 33 窗口3售票,票号为:68
 34 窗口3售票,票号为:67
 35 窗口3售票,票号为:66
 36 窗口3售票,票号为:65
 37 窗口3售票,票号为:64
 38 窗口3售票,票号为:63
 39 窗口3售票,票号为:62
 40 窗口3售票,票号为:61
 41 窗口3售票,票号为:60
 42 窗口3售票,票号为:59
 43 窗口3售票,票号为:58
 44 窗口3售票,票号为:57
 45 窗口3售票,票号为:56
 46 窗口3售票,票号为:55
 47 窗口3售票,票号为:54
 48 窗口3售票,票号为:53
 49 窗口3售票,票号为:52
 50 窗口3售票,票号为:51
 51 窗口3售票,票号为:50
 52 窗口3售票,票号为:49
 53 窗口3售票,票号为:48
 54 窗口3售票,票号为:47
 55 窗口3售票,票号为:46
 56 窗口3售票,票号为:45
 57 窗口3售票,票号为:44
 58 窗口3售票,票号为:43
 59 窗口3售票,票号为:42
 60 窗口3售票,票号为:41
 61 窗口3售票,票号为:40
 62 窗口3售票,票号为:39
 63 窗口3售票,票号为:38
 64 窗口3售票,票号为:37
 65 窗口3售票,票号为:36
 66 窗口3售票,票号为:35
 67 窗口3售票,票号为:34
 68 窗口3售票,票号为:33
 69 窗口3售票,票号为:32
 70 窗口3售票,票号为:31
 71 窗口3售票,票号为:30
 72 窗口3售票,票号为:29
 73 窗口3售票,票号为:28
 74 窗口3售票,票号为:27
 75 窗口3售票,票号为:26
 76 窗口3售票,票号为:25
 77 窗口3售票,票号为:24
 78 窗口3售票,票号为:23
 79 窗口3售票,票号为:22
 80 窗口3售票,票号为:21
 81 窗口3售票,票号为:20
 82 窗口3售票,票号为:19
 83 窗口3售票,票号为:18
 84 窗口3售票,票号为:17
 85 窗口3售票,票号为:16
 86 窗口3售票,票号为:15
 87 窗口3售票,票号为:14
 88 窗口3售票,票号为:13
 89 窗口3售票,票号为:12
 90 窗口3售票,票号为:11
 91 窗口3售票,票号为:10
 92 窗口3售票,票号为:9
 93 窗口3售票,票号为:8
 94 窗口3售票,票号为:7
 95 窗口3售票,票号为:6
 96 窗口3售票,票号为:5
 97 窗口3售票,票号为:4
 98 窗口3售票,票号为:3
 99 窗口3售票,票号为:2
100 窗口3售票,票号为:1
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】尚硅谷Java视频_深入浅出、兼顾实战的Java基础视频(课堂实录)

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

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

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

你可能感兴趣的:(Java入门3.1---多线程)