Java进程,多线程详解

Posted seeyoumiter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java进程,多线程详解相关的知识,希望对你有一定的参考价值。

?      在我们平时使用Windows时,会看到同时运行多个应用程序的假象,这实际上是CPU切换进程的频率很快,导致我们没有察觉。实际上,CPU每一段时间只能运行一个应用程序。 

进程与线程

一个操作系统可以有多个进程,进程可以简单的看做是正在执行中的应用程序。进程是多个线程的集合,一个进程中至少有一个线程,我们知道,代码是从上往下执行的,我们平时看到的main函数就是程序的主线程。

至于线程,也可以简单的看作是程序中一条代码的执行路径,执行顺序。

理解线程,可以看一下同步与异步操作的概念:



线程创建

使用多线程的目的:

使用多线程, 可以提高程序效率,每个线程互不影响,都可以自己独立运行。

线程的创建有哪些方式:

  1. 使用继承Thread类方式

  2. 使用实现Runable接口方式

  3. 使用匿名内部类

  4. callable

  5. 使用线程池创建线程

使用继承Thread类的方式还是实现Runnable接口的方式来实现多线程会比较好?

使用实现Runnable接口的方式更好,广大java开发者大都是面向接口编程,且接口可以多重实现,这很好的弥补了java中不支持多继承的缺陷。

 1/* 使用继承Thread类方式实现多线程 */
2
3class CreateThreadDemo01 extends Thread {
4    @Override
5    public void run() {
6        for(int i = 0; i < 30; i++) {
7            System.out.println("当前线程的ID: " + getId() + ", 当前线程名: " + Thread.currentThread().getName() + "i: " + i );
8        }
9    }
10}
11
12public class ThreadDemo01 {
13    public static void main(String[] args) {
14        CreateThreadDemo01 ct1 = new CreateThreadDemo01();
15        ct1.start();    // 启动线程调用的是start(),而不是run()方法
16        for(int i = 0; i < 30; i++) {
17            System.out.println("主线程的ID: " + Thread.currentThread().getId() +  ", 当前线程名: " + Thread.currentThread().getName() + "i: " + i);
18        }
19    }
20}
 1/* 使用实现Runnable接口的方式实现多线程 */
2
3class CreateThreadDemo02 implements Runnable {
4    @Override
5    public void run() {
6        for(int i = 0; i < 30; i++) {
7            try {
8                Thread.sleep(1000); // 每隔一秒执行一遍
9            } catch(Exception e) {
10                // TODO:
11            }
12            System.out.println("子线程 run: " + i);
13            if(i == 5) {
14                Thread.currentThread().stop();  // 这种结束线程的方法是不安全的(不考虑代码是否已经执行完毕直接关闭线程,会导致出错)
15            }
16        }
17    }
18}
19
20public class ThreadDemo02 {
21    public static void main(String[] args) {
22        CreateThreadDemo02 ct2 = new CreateThreadDemo02();
23        Thread t = new Thread(ct2);
24        t.start();
25        for(int i = 0; i < 30; i++) {
26            System.out.println("主线程");
27        }
28    }
29}
 1/* 使用匿名内部类的方式创建多线程 */
2
3abstract class Test {
4    abstract public void add();
5}
6
7public class ThreadDemo03 {
8    public static void main(String[] args{
9        /* 匿名内部类用法示例:
10            Test test = new Test() {
11                public void add() {
12                    System.out.println("匿名内部类");
13                }
14            };
15            test.add();
16        */

17        Thread t = new Thread(new Runnable() {
18            public void run() {
19                for(int i = 0; i < 30; i++) {
20                    System.out.println("子线程 " + i);
21                }
22            }
23        });
24        /* 等价于:
25            class CreateThreadDemo03 implements Runnable {
26                public void run() {
27                    // TODO: 
28                }
29            }
30            CreateThreadDemo03 ct3 = new CreateThreadDemo03();
31            Thread t = new Thread(ct3);
32        */

33        t.start();
34        for(int i = 0; i < 30; i++) {
35            System.out.println("主线程");
36        }
37    }
38}

关于线程的概念过于抽象,下面以一张图来体现线程的具体表现:



线程守护



 1public class ThreadDemo04 {
2    public static void main(String[] args{
3        Thread t = new Thread(new Runnable() {  // 由主线程产生的子线程是用户进程,即非守护进程,与主线程外关
4            public void run() {
5                for(int i = 0; i < 30; i++) {
6                    try {
7                        Thread.sleep(300);
8                    } catch(Exception e) {}
9                    System.out.println("子线程 " + i);
10                }
11            }
12        });
13        // t.setDaemon(true);   // 把t线程设为守护线程,同主线程一起销毁
14        t.start();
15        for(int i = 0; i < 5; i++) {
16            try {
17                Thread.sleep(300);
18            } catch(Exception e) {}
19            System.out.println("主线程 " + i);
20        }
21        System.out.println("主线程执行完毕....");
22    }
23}

多线程的状态

多线程共有以下几种状态:

  • 新建 | 初始

  • 准备|就绪 | 可运行

  • 运行

  • 休眠

  • 停止 | 结束



线程类的start()方法调用后并不是立即执行多线程代码,而是使得该线程变为就绪状态,而至于什么时候正式运行是由操作系统的调度所决定的。



join()

 1public class ThreadDemo06 {
2    public static void main(String[] args{
3        // t1 => t2 => t3
4        Thread t1= new Thread(new Runnable() {
5                @Override
6            public void run() 
{
7                for(int i = 0; i < 30; i++) {
8                    System.out.println("子线程1: " + i);
9                }
10            }
11        });
12        t1.start();
13
14        Thread t2= new Thread(new Runnable() {
15            @Override
16            public void run() 
{
17                try {
18                    t1.join();      // 当前线程t2的控制权让给t1线程(t2进入就绪状态,等t1线程执行完毕后再回过头来执行t2线程
19                } catch(Exception e) {}
20                for(int i = 0; i < 30; i++) {
21                    System.out.println("子线程2: " + i);
22                }
23            }
24        });
25        t2.start();
26
27        Thread t3= new Thread(new Runnable() {
28            @Override
29            public void run() 
{
30                try {
31                    t2.join();
32                } catch(Exception e) {}
33                for(int i = 0; i < 30; i++) {
34                    System.out.println("子线程3: " + i);
35                }
36            }
37        });
38        t3.start();
39        for(int i = 0; i < 30; i++) {
40            System.out.println("主线程:" + i);
41        }
42    }
43}

线程安全

当多个线程同时共享同一个全局变量时,做写入操作时,可能会受到其他线程的干扰,导致数据有问题,这种现象叫做线程安全问题。做读入操作时,则不会产生数据冲突的问题。

下面来看一个例子了解下什么是线程安全问题:

 1class threadTrain implements Runnable {
2    private int train1Count = 20;
3
4    @Override
5    public void run() {
6        // int train1Count = 100;   // 多个线程共享一个局部变量时不会产生线程安全问题(每个线程都有100张票)
7        while(train1Count > 0) {
8            try {
9                Thread.sleep(50);
10            } catch(Exception e) {
11                // TODO: Handle Exception
12            }
13            System.out.println(Thread.currentThread().getName() + "正在出售第" + (20 - train1Count+1) + "张票");
14            train1Count--;
15        }
16    }
17}
18
19public class ThreadDemo07 {
20    public static void main(String[] args) {
21        threadTrain tt = new threadTrain();
22        // threadTrain tt2 = new threadTrain();
23        // 多个线程共享同一个全局变量时,由于多个线程间没有进行通信,这个全局变量的值在使用时可能受到其他线程的干扰
24        Thread t1 = new Thread(tt, "窗口1");      // new Thread(该线程要执行的操作, 线程名)
25        Thread t2 = new Thread(tt, "窗口2");
26        t1.start();
27        t2.start();
28    }
29}
30
31/* 输出:(多个线程抢同一张票)
32    窗口2正在出售第1张票
33    窗口1正在出售第1张票
34    窗口2正在出售第3张票
35    窗口1正在出售第3张票
36    窗口2正在出售第5张票
37    窗口1正在出售第5张票
38    窗口2正在出售第7张票
39    窗口1正在出售第7张票
40    窗口2正在出售第9张票
41    窗口1正在出售第10张票
42    窗口2正在出售第11张票
43    窗口1正在出售第11张票
44    窗口1正在出售第13张票
45    窗口2正在出售第13张票
46    窗口2正在出售第15张票
47    窗口1正在出售第15张票
48    窗口2正在出售第17张票
49    窗口1正在出售第17张票
50    窗口1正在出售第19张票
51    窗口2正在出售第19张票
52    窗口1正在出售第21张票
53*/

那么如何解决线程安全问题,线程如何同步保证数据的原子性?

答案是使用线程锁,java中有两种线程锁,synchronized和lock。

synchronized



上面的例子通过synchronized锁解决线程安全问题的代码如下:

 1class threadTrain implements Runnable {
2    private int train1Count = 20;
3    private Object obj = new Object();
4
5    @Override
6    public void run() {
7        // int train1Count = 100;   // 多个线程共享一个局部变量时不会产生线程安全问题(每个线程都有100张票)
8        while(train1Count > 0) {
9            try {
10                Thread.sleep(50);
11            } catch(Exception e) {
12                // TODO: Handle Exception
13            }
14            sale();
15        }
16    }
17
18    public void sale() {
19        // 同步代码块
20        synchronized(obj) { // 将可能会导致线程安全的代码放在synchronizd代码块内
21            // 线程锁内只能有一个线程进行访问,其他线程只能等待。当线程锁释放时,必须拿到锁才可以访问
22            if(trainCount > 0)
23                System.out.println(Thread.currentThread().getName() + "正在出售第" + (20 - train1Count+1) + "张票");
24                train1Count--;
25            }
26        }
27    }
28}
29
30public class ThreadDemo07 {
31    public static void main(String[] args) {
32        threadTrain tt = new threadTrain();
33        // threadTrain tt2 = new threadTrain();
34        // 多个线程共享同一个全局变量
35        Thread t1 = new Thread(tt, "窗口1");      // new Thread(该线程要执行的操作, 线程名)
36        Thread t2 = new Thread(tt, "窗口2");
37        t1.start();
38        t2.start();
39    }
40}
41
42/* 输出:
43    窗口2正在出售第1张票
44    窗口1正在出售第2张票
45    窗口2正在出售第3张票
46    窗口1正在出售第4张票
47    窗口2正在出售第5张票
48    窗口1正在出售第6张票
49    窗口1正在出售第7张票
50    窗口2正在出售第8张票
51    窗口2正在出售第9张票
52    窗口1正在出售第10张票
53    窗口2正在出售第11张票
54    窗口1正在出售第12张票
55    窗口1正在出售第13张票
56    窗口2正在出售第14张票
57    窗口2正在出售第15张票
58    窗口1正在出售第16张票
59    窗口2正在出售第17张票
60    窗口1正在出售第18张票
61    窗口2正在出售第19张票
62    窗口1正在出售第20张票
63*/

使用线程锁将执行流转换为顺序同步流后,会导致程序的执行效率变低(其他线程都在等待且总是需要判断是哪个线程)

当锁内代码执行完毕或抛出异常时锁都会被释放掉。

同步函数

 1class threadTrain implements Runnable {
2    private int train1Count = 20;
3
4    @Override
5    public void run() {
6        while(train1Count > 0) {
7            try { 
8                Thread.sleep(50);
9            } catch(Exception e) {
10                // TODO: Handle Exception
11            }
12            sale();
13        }
14    }
15
16    // 同步函数
17    // 同一时期只能有一个线程执行该函数
18    public synchronized void sale() {
19        // 同步函数可以看做是this锁即synchronized(this){}
20        System.out.println(Thread.currentThread().getName() + "正在出售第" + (20 - train1Count+1) + "张票");
21        train1Count--;
22    }
23}
24
25public class ThreadDemo07 {
26    public static void main(String[] args) throws InterruptedException {
27        threadTrain tt = new threadTrain();
28        Thread t1 = new Thread(tt, "窗口1");
29        Thread t2 = new Thread(tt, "窗口2");
30        t1.start();
31        Thread.sleep(400);
32        t2.start();
33    }
34}

当多个线程争 同一把锁(只有多个线程使用同一把锁才能保证线程同步)时,可以实现多线程同步(如同步函数和this锁),但如果这几个线程用的不是同一把锁(如同步函数与obj锁),则不会如期的实现多线程同步。

静态同步函数

同步函数使用的是this锁,静态同步函数因为没有this,使用的不是this锁。静态函数在class文件加载时就是

 1class threadTrain implements Runnable {
2    private static int train1Count = 100;
3    public boolean flag = true;
4
5    @Override
6    public void run() {
7        if(flag) {
8            while(train1Count > 0) {
9                synchronized(this) {        // 静态同步函数使用的是synchronzied(threadTrain.class)这把锁
10                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (100 - train1Count+1) + "张票");
11                    train1Count--;
12                }
13            }
14        } else {
15            while(train1Count > 0) {
16                sale();
17            }
18        }
19    }
20
21    // 静态同步函数
22    public static synchronized void sale() {
23        System.out.println(Thread.currentThread().getName() + "正在出售第" + (100 - train1Count+1) + "张票");
24        train1Count--;
25    }
26}
27
28public class ThreadDemo07 {
29    public static void main(String[] args) throws InterruptedException {
30        threadTrain tt = new threadTrain();
31        Thread t1 = new Thread(tt, "窗口1");
32        Thread t2 = new Thread(tt, "窗口2");
33        t1.start();
34        Thread.sleep(400);
35        tt.flag = false;
36        t2.start();
37    }
38}

Java内存模型

Java内存模型(简称JMM)与Java内存结构不同,Java内存模型是多线程概念中的,决定了一个线程与另一个线程是否可见,而Java内存结构指的的JVM堆栈内存分配,这两者是风马牛不相及的。

多线程三大特性:

  • 原子性: 共享全局数据的一个值在多个线程中只使用一次

  • 可见性: 线程的本地内存数据更改后马上刷新到主内存区并通知另外与之并行的线程

  • 有序性: 使用join,wait,nofity等进行线程间通信




valotile

valotile可以使多个线程中产生可见性。(但不具备原子性,只是强制刷新主内存,但不保证读到的数据一个是正确的)

 1class ThreadVolatileDemo extends Thread {
2    public /* volatile */ boolean flag = true;
3
4    @Override
5    public void run() {
6        System.out.println("子线程开始执行...");
7        while(flag) {
8
9        }
10        System.out.println("子线程执行结束");
11    }
12
13    public void setFlag(boolean flag) {
14        this.flag = flag;
15    }
16}
17
18public class ThreadDemo05 {
19    public static void main(String[] args) throws InterruptedException {
20        ThreadVolatileDemo tv= new ThreadVolatileDemo();
21        tv.start();
22        Thread.sleep(3000);
23        tv.setFlag(false);      // 两个线程main和tv,主线程main修改tv的flag值
24        /* 
25             正常情况下,主线程更新tv的值后,会马上通知到tv,但由于使主线程休眠了3s,
26         导致通知不及时,这时tv的本地内存区使用的还是原来的值,
27         于是我们只能使用volatile强制使main线程实时通知到tv线程。
28        */

29        System.out.println("flag已经修改false");
30        Thread.sleep(1000);
31        System.out.println(tv.flag);
32    }
33}

多线程通信

 1class Res {
2    public String Username;
3    public String sex;
4}
5
6class Out extends Thread {
7    Res res = new Res();
8    public Out(Res res) {
9        this.res = res;
10    }
11
12    public void run() {
13        int count = 0;
14        while(true) {
15            if(count % 2 == 0) {
16                res.Username = "小红";
17                res.sex = "女";
18            } else {
19                res.Username = "小余";
20                res.sex = "男";
21            }
22            count = (count+1) % 2;
23        }
24    }
25}
26
27class Input extends Thread {
28    Res res;
29    public Input(Res res) {
30        this.res = res;
31    }
32
33    public void run() {
34        while(true) {
35            System.out.println(res.Username + ", " + res.sex);
36        }
37    }
38}
39
40public class OutInputThread {
41    public static void main(String[] args) {
42        Res res = new Res();
43        Out out = new Out(res);
44        Input input = new Input(res);
45        out.start();
46        input.start();
47    }
48}
49
50/* 输出:
51小红 女
52小余 男
53小红 男
54小余 女
55*/

 

 

以上是关于Java进程,多线程详解的主要内容,如果未能解决你的问题,请参考以下文章

java 多线程详解

Java多线程详解总结

Java多线程详解1面试+工作

java线程详解

Java 多线程详解------如何创建进程和线程

Java多线程详解