多线程(Multi-Thread)

线程概念

  1. 什么是线程:
    线程就是程序中单独顺序的流控制。线程本身不能运行,它只能用于程序中。
    在这里插入图片描述
  2. 什么是多线程:
    多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务.
    在这里插入图片描述
  3. 说明:线程是程序内的顺序控制流, 只能使用分配给程序的资源和环境。

多线程编程的目的

多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

进程

进程:执行中的程序(程序是静态的概念,进程是动态的概念)。
在这里插入图片描述

单线程

  1. 当程序启动运行时,就自动产生一个线程,主方法main就在这个主线程上运行
  2. Java 中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main 方法就在主线程上运行), 我们的程序都是由线程来执行的。

多线程

  1. 一个进程可以包含一个或多个线程
  2. 一个程序实现多个代码同时交替运行就需要产生多个线程
  3. CPU随机的抽出时间,让我们的程序一会做这件事情,一会做另外一件事情
  4. 同其他大多数编程语言不同, Java 内置多线程编程(multithreaded programming)。多线程程序包含两条或两条以上并发运行的部分,把程序中样的部分都叫作一个线程(thread)每个线程都有独立的执行路径,因此多多任务处理的一种特殊形式。
  5. 多任务处理被所有的现代操作系统所支持。然而, 多任务处理有两种截然不同的类型:基于进程的和基于线程的。
  6. 基于进程的多任务处理是更熟悉的形式。 进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。
  7. 而在基于线程(thread-based)的多任务处理环境中, 线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。 例如,一个文本编辑器可以在打印的同时格式化文本。

线程与进程的区别

  1. 多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.
  2. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈, 所以线程的切换比进程切换的负担要小。

Process vs Thread

  1. 多线程程序比多进程程序需要更少的管理费用。 进程是重量级的任务,需要分配给它们独立的地址空间。 进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。 另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。
  2. 多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很多。在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步—尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。

Java线程模型

Java多线程的优点就在于取消了主循环/轮询机制。一个线程可以暂停而不影响程序的其他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。

多线程的优势

  1. Java运行系统在很多方面依赖于线程, 所有的类库设计都考虑到多线程。实际上, Java使用线程来使整个环境异步。这有利于通过防止CPU循环的浪费来减少无效部分。
  2. 为更好地理解多线程环境的优势, 我们可以将它与它的对照物相比较。 单线程系统的处理途径是使用一种叫作轮询的事件循环方法。 在该模型中, 单线程控制在一无限循环中运行, 轮询一个事件序列来决定下一步做什么。 一旦轮询装置返回信号表明已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。 直到事件处理程序返回, 系统中没有其他事件发生。 这就浪费了CPU时间。 这导致了程序的一部分独占了系统, 阻止了其他事件的执行。 总的来说, 单线程环境, 当一个线程因为等待资源时阻塞(block, 挂起执行) , 整个程序停止运行。

线程的实现

  1. 在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。
    1. 继承Thread类并重写run方法。
    2. 通过定义实现Runnable接口的类进而实现run方法。
  2. 继承Thread类并重写run方法。
    1. Thread类:是专门用来创建线程和对线程进行操作的类。 Thread中定义了许多方法对线程进行操作。
    2. Thread类在缺省情况下run方法什么都不做。可以通过继承Thread类并重写Thread类的run方法实现用户线程。
  3. 继承Thread类并重写run方法,但是我们用start方法启动run方法,注意不要重写start方法。总体结构如下:
public class MyThread extends Thread {
public void run() {
… …
}
}
MyThread t = new MyThread();
t. start();
package JavaBase.multithread;

public class ThreadTest {
    public static void main(String[] args) {
        //声明线程对象
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        //线程启动后就不受控制了,执行是随机的。
        thread1.start();
        thread2.start();
        //如果调用run方法,就不会以线程的方式运行了,会把它当作一个类来运行。
    }
}
//继承Thread,并重写run方法,绝对不要重写start方法
class Thread1 extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("hello world:" + i);
        }
    }
}

class Thread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("welcome:" + i);
        }
    }
}

结果是:
welcome:0
welcome:1
welcome:2
welcome:3
welcome:4
hello world:0
welcome:5
hello world:1
welcome:6
hello world:2
welcome:7
hello world:3
welcome:8
hello world:4
welcome:9
welcome:10
welcome:11
welcome:12
welcome:13
welcome:14
welcome:15
welcome:16
hello world:5
welcome:17
welcome:18
welcome:19
hello world:6
hello world:7
hello world:8
welcome:20
welcome:21
welcome:22
welcome:23
welcome:24
welcome:25
welcome:26
welcome:27
welcome:28
welcome:29
welcome:30
welcome:31
welcome:32
welcome:33
welcome:34
welcome:35
welcome:36
welcome:37
welcome:38
welcome:39
welcome:40
welcome:41
welcome:42
hello world:9
welcome:43
welcome:44
welcome:45
welcome:46
welcome:47
hello world:10
welcome:48
welcome:49
welcome:50
welcome:51
welcome:52
welcome:53
welcome:54
welcome:55
welcome:56
welcome:57
welcome:58
welcome:59
hello world:11
welcome:60
welcome:61
welcome:62
welcome:63
welcome:64
welcome:65
welcome:66
welcome:67
welcome:68
welcome:69
hello world:12
hello world:13
hello world:14
hello world:15
hello world:16
hello world:17
hello world:18
welcome:70
hello world:19
hello world:20
hello world:21
hello world:22
welcome:71
hello world:23
hello world:24
hello world:25
hello world:26
hello world:27
hello world:28
hello world:29
hello world:30
hello world:31
hello world:32
hello world:33
hello world:34
hello world:35
hello world:36
hello world:37
hello world:38
hello world:39
welcome:72
hello world:40
hello world:41
hello world:42
hello world:43
hello world:44
hello world:45
hello world:46
hello world:47
hello world:48
hello world:49
hello world:50
hello world:51
hello world:52
welcome:73
hello world:53
hello world:54
hello world:55
hello world:56
welcome:74
welcome:75
welcome:76
welcome:77
welcome:78
welcome:79
welcome:80
welcome:81
hello world:57
welcome:82
welcome:83
welcome:84
welcome:85
hello world:58
welcome:86
hello world:59
hello world:60
hello world:61
hello world:62
hello world:63
hello world:64
hello world:65
hello world:66
hello world:67
hello world:68
hello world:69
hello world:70
hello world:71
hello world:72
hello world:73
hello world:74
hello world:75
hello world:76
hello world:77
hello world:78
hello world:79
hello world:80
hello world:81
hello world:82
hello world:83
hello world:84
hello world:85
hello world:86
hello world:87
hello world:88
hello world:89
hello world:90
welcome:87
hello world:91
hello world:92
hello world:93
hello world:94
hello world:95
hello world:96
hello world:97
hello world:98
hello world:99
welcome:88
welcome:89
welcome:90
welcome:91
welcome:92
welcome:93
welcome:94
welcome:95
welcome:96
welcome:97
welcome:98
welcome:99

将我们希望线程执行的代码放到 run 方法中,然后通过 start 方法来启动线程, start方法首先为线程的执行准备好系统资源,然后再去调用 run 方法。 当某个类继承了Thread 类之后,该类就叫做一个线程类。

  1. 一个进程至少要包含一个线程。
  2. 对于单核 CPU 来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。
  3. 对于双核或双核以上的 CPU 来说,可以真正做到微观并行。
  4.   1. Thread 类也实现了 Runnable 接口,因此实现了 Runnable
      2. 当生成一个线程对象时,如果没有为其设定名字,那么线形式: Thread-number,该 number 将是自动增加的,并被(因为它是 static 的成员变量)。
      3. 当使用第一种方式来生成线程对象时,我们需要重写 run方法此时什么事情也不做。
      4. 当使用第二种方式来生成线程对象时,我们需要实现 Runnable 接口的 run 方法,然后使用 new Thread(new MyThread())(假如 MyThread 已经实现了 Runnable 接口) 来生成线程对象,这时的线程对象的 run 方法就会调用 MyThread 类的 run 方法,这样我们自己编写的 run 方法就执行了。
    
  5. 实现Runnable接口的类实现run方法。通过建立一个实现了Runnable接口的类,并以它作为线程的目标对象来创建一个线程。
    Runnable接口:定义了一个抽象方法run()。定义如下:
    public interface java.lang.Runnable{
    public abstract void run();}
  6. 实现Runnable接口的类实现run方法。
    创建的总体框架如下:
 class MyRunner implements Runnable {
public void run() {}
}
MyRunner r = new MyRunner();
Thread t = new Thread( ThreadGroup group, Runnable target, String name);
例如: Thread t = new Thread( r, "aa");
package JavaBase.multithread;

public class ThreadTest2 {
    public static void main(String[] args) {
        /*Thread thread = new Thread(new MyThread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("hello:" + i);
                }
            }
        });
        thread.start();*/

        Thread thread = new Thread(new MyThread());
        thread.start();
        Thread thread1 = new Thread(new MyThread2());
        thread1.start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("hello:" + i);
        }
    }
}

class MyThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("welocme:" + i);
        }
    }
}

结果是:
hello:0
hello:1
hello:2
hello:3
hello:4
hello:5
hello:6
hello:7
hello:8
hello:9
hello:10
hello:11
hello:12
hello:13
hello:14
hello:15
hello:16
hello:17
hello:18
hello:19
hello:20
hello:21
hello:22
hello:23
hello:24
hello:25
hello:26
hello:27
hello:28
hello:29
hello:30
hello:31
hello:32
welocme:0
welocme:1
welocme:2
welocme:3
welocme:4
welocme:5
welocme:6
welocme:7
welocme:8
welocme:9
welocme:10
hello:33
welocme:11
welocme:12
welocme:13
welocme:14
welocme:15
welocme:16
welocme:17
welocme:18
hello:34
hello:35
hello:36
hello:37
hello:38
hello:39
hello:40
hello:41
hello:42
hello:43
hello:44
welocme:19
welocme:20
hello:45
hello:46
hello:47
hello:48
hello:49
hello:50
hello:51
hello:52
hello:53
hello:54
hello:55
hello:56
hello:57
hello:58
hello:59
hello:60
welocme:21
welocme:22
welocme:23
welocme:24
hello:61
hello:62
hello:63
hello:64
hello:65
hello:66
hello:67
hello:68
hello:69
hello:70
hello:71
hello:72
hello:73
hello:74
hello:75
hello:76
hello:77
welocme:25
hello:78
hello:79
hello:80
hello:81
hello:82
hello:83
hello:84
hello:85
hello:86
welocme:26
welocme:27
hello:87
hello:88
hello:89
hello:90
welocme:28
welocme:29
welocme:30
hello:91
welocme:31
welocme:32
welocme:33
welocme:34
welocme:35
welocme:36
welocme:37
welocme:38
welocme:39
welocme:40
welocme:41
welocme:42
welocme:43
welocme:44
welocme:45
welocme:46
welocme:47
welocme:48
welocme:49
welocme:50
welocme:51
welocme:52
welocme:53
welocme:54
welocme:55
welocme:56
welocme:57
welocme:58
welocme:59
hello:92
hello:93
hello:94
hello:95
hello:96
hello:97
hello:98
hello:99
welocme:60
welocme:61
welocme:62
welocme:63
welocme:64
welocme:65
welocme:66
welocme:67
welocme:68
welocme:69
welocme:70
welocme:71
welocme:72
welocme:73
welocme:74
welocme:75
welocme:76
welocme:77
welocme:78
welocme:79
welocme:80
welocme:81
welocme:82
welocme:83
welocme:84
welocme:85
welocme:86
welocme:87
welocme:88
welocme:89
welocme:90
welocme:91
welocme:92
welocme:93
welocme:94
welocme:95
welocme:96
welocme:97
welocme:98
welocme:99

总结:

  1. 两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。
  2. 在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。
  3. 线程的消亡不能通过调用一个stop()命令。而是让run()方法自然结束。

停止线程

public class MyThread implements Runnable{ 
private boolean flag=truepublic void run()
{ while (flag)
{}
}
public void stopRunning()
{ flag=false;}
}
public class ControlThread
{ private Runnable r=new MyThread();
private Thread t=new Thread(r);
public void startThread()
{ t.start(); }
publi void stopThread()
{ r.stopRunning();}
}

线程的生命周期

线程的生命周期: 一个线程从创建到消亡的过程。
线程的生命周期可分为四个状态:

  1. 创建状态
  2. 可运行状态
  3. 不可运行状态
  4. 消亡状态
    线程的状态转换图:
    在这里插入图片描述
    Runnable:可运行状态
    Running:运行状态
    Block:阻塞状态

创建状态

当用new操作符创建一个新的线程对象时,该线程处于创建状态。
处于创建状态的线程只是一个空的线程对象,系统不为它分配资源

可运行状态

执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可行( Runnable )状态。
这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。

不可运行状态

当发生下列事件时,处于运行状态的线程会转入到不可运行状态。
• 调用了sleep()方法,传入的参数是毫秒;
• 线程调用wait方法等待特定条件的满足
• 线程输入/输出阻塞

返回可运行状态:

• 处于睡眠状态的线程在指定的时间过去后
• 如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变
• 如果线程是因为输入/输出阻塞,等待输入/输出完成

消亡状态

当线程的run方法执行结束后,该线程自然消亡。(线程死了不能复生,也就是说消亡后不能再start()了)

线程的优先级

  1. 线程的优先级及其设置设置优先级是为了在多线程环境中便于系统程的调度,优先级高的线程将优先执行一个线程的优先级设置遵从以下原则:
    _ 线程创建时,子继承父的优先级
    – 线程创建后,可通过调用setPriorit法改变优先级。
    – 线程的优先级是1-10之间的正整数。
    1 - MIN_PRIORITY,
    10 – MAX_PRIORITY
    5- NORM_PRIORITY
  2. 线程的调度策略
    线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。
    • 线程体中调用了yield()方法,让出了对CPU的占用权
    • 线程体中调用了sleep()方法, 使线程进入睡眠状态
    • 线程由于I/O操作而受阻塞
    • 另一个更高优先级的线程出现。
    • 在支持时间片的系统中,该线程的时间片用完。
package JavaBase.multithread;

public class ThreadTest3 {
    public static void main(String[] args) {
        Runnable runnable = new HelloThread();
        Thread thread = new Thread(runnable);
        Thread thread1 = new Thread(runnable);

        thread.start();
        thread1.start();

    }

}

class HelloThread implements Runnable{
    //如果我们使用成员变量i,会输出50个,因为一个对象共用一个成员变量
    int i;

    @Override
    public void run() {
        //如果我们使用局部变量i,会输出2个50,因为局部变量是每个线程一个,互不影响
//        int i = 0;
        
        while (true) {
            System.out.println("number:" + i++);

            try {
                Thread.sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (50 == i) {
                break;
            }
        }
    }
}
  1. 关于成员变量与局部变量: 如果一个变量是成员变量, 那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
  2. 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
  3. 停止线程的方式:不能使用 Thread 类的 stop 方法来终止线程的执行。一般要设定一个变量,在 run 方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
  4. 不能依靠线程的优先级来决定线程的执行顺序。
  5. Thread的一些常用方法。
    • 测试 threads:
    isAlive()
    • Thread priority:
    t getPriority()
    t setPriority()
    • threads 进入非执行状态
    Thread. sleep()
    Thread. yield()

多线程的同步

为什么要引入同步机制

在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

怎样实现同步

  1. 对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized
  2. synchronized 关键字: 当 synchronized 关键字修饰一个方法的时候,该方法叫做同步方法。
package JavaBase.multithread;

public class FetchMoney {

    public static void main(String[] args) {
        Bank bank = new Bank();

        Thread thread1 = new MoneyThread(bank);//柜台
        Thread thread2 = new MoneyThread(bank);//提款机

        thread1.start();
        thread2.start();
    }
}

class Bank{
    private static int money = 1000;

    public synchronized int getMoney(int number) {
        if (number < 0) {
            return -1;
        } else if (number > money) {
            return -2;
        } else if (money < 0) {
            return -3;
        } else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            money -= number;

            System.out.println("left money:" + money);
            return number;
        }
    }
}

class MoneyThread extends Thread{
    private Bank bank;

    public MoneyThread(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        System.out.println(bank.getMoney(800));
    }
}

结果是:
left money:200
800
-2

  1. Java 中的每个对象都有一个锁(lock) 或者叫做监视器(monitor),当访问某个对象的 synchronized 方法时, 表示将该对象上锁,此时其他任何线程都无法再去访问该 synchronized 方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该 synchronized 方法。
package JavaBase.multithread;

public class ThreadTest4 {
    public static void main(String[] args) {
        Example example = new Example();

        Thread thread1 = new TheThread(example);

        example = new Example();

        Thread thread2 = new TheThread2(example);

        thread1.start();
        thread2.start();

    }
}

class Example{
    public synchronized void execute(){
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello:" + i);
        }
    }
    public synchronized void execute2() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("world:" + i);
        }

    }
}

class TheThread extends Thread{
    private Example example;

    public TheThread(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute();
    }
}

class TheThread2 extends Thread{
    private Example example;

    public TheThread2(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute2();
    }
}
结果是:
world:0
hello:0
hello:1
world:1
hello:2
world:2
hello:3
hello:4
world:3
world:4
hello:5
world:5
world:6
hello:6
hello:7
world:7
world:8
world:9
world:10
hello:8
world:11
hello:9
hello:10
hello:11
hello:12
world:12
world:13
hello:13
world:14
world:15
hello:14
hello:15
world:16
hello:16
hello:17
world:17
hello:18
world:18
hello:19
world:19

如果一个对象有多个 synchronized 方法,某一时刻某个线程已经进入到了某个synchronized 方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何 synchronized 方法的。

package JavaBase.multithread;

public class ThreadTest4 {
    public static void main(String[] args) {
        Example example = new Example();

        Thread thread1 = new TheThread(example);

        example = new Example();

        Thread thread2 = new TheThread2(example);

        thread1.start();
        thread2.start();

    }
}

class Example{
    //如果只有一个static也是乱序的,因为他锁的是class对象,并没有锁这个对象本身
    public synchronized static void execute(){
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello:" + i);
        }
    }
    public synchronized static void execute2() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("world:" + i);
        }

    }
}

class TheThread extends Thread{
    private Example example;

    public TheThread(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute();
    }
}

class TheThread2 extends Thread{
    private Example example;

    public TheThread2(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute2();
    }
}
结果是:
hello:0
hello:1
hello:2
hello:3
hello:4
hello:5
hello:6
hello:7
hello:8
hello:9
hello:10
hello:11
hello:12
hello:13
hello:14
hello:15
hello:16
hello:17
hello:18
hello:19
world:0
world:1
world:2
world:3
world:4
world:5
world:6
world:7
world:8
world:9
world:10
world:11
world:12
world:13
world:14
world:15
world:16
world:17
world:18
world:19

如果某个 synchronized 方法是 static 的,那么当线程访问该方法时,它锁的并不是synchronized 方法所在的对象,而是 synchronized 方法所在的对象所对应的 Class 对象,因为 Java 中无论一个类有多少个对象,这些对象会对应唯一一个 Class 对象,因此当线程分别访问同一个类的两个对象的两个 static, synchronized 方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

  1. synchronized 块,写法:
    synchronized(object)
    { }
    表示线程在执行的时候会对 object 对象上锁。
package JavaBase.multithread;

import java.io.ObjectInputStream;

public class ThreadTest5 {
    public static void main(String[] args) {
        Example2 example2 = new Example2();

        Thread thread1 = new TheThread3(example2);
        Thread thread2 = new TheThread4(example2);

        thread1.start();
        thread2.start();
    }
}
class Example2{
    private Object object = new Object();
    public void execute(){
        //synchronized块,传一个任意的对象,会将这个对象锁上
        synchronized (this) {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep((long)(Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("hello:" + i);
            }
        }
    }
    public void execute2() {
        synchronized (this) {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep((long) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("world:" + i);
            }
        }
    }
}
class TheThread3 extends Thread{
    private Example2 example;

    public TheThread3(Example2 example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute();
    }
}

class TheThread4 extends Thread{
    private Example2 example;

    public TheThread4(Example2 example) {
        this.example = example;
    }

    @Override
    public void run() {
        this.example.execute2();
    }
}

结果是:
hello:0
hello:1
hello:2
hello:3
hello:4
hello:5
hello:6
hello:7
hello:8
hello:9
hello:10
hello:11
hello:12
hello:13
hello:14
hello:15
hello:16
hello:17
hello:18
hello:19
world:0
world:1
world:2
world:3
world:4
world:5
world:6
world:7
world:8
world:9
world:10
world:11
world:12
world:13
world:14
world:15
world:16
world:17
world:18
world:19

synchronized 方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized 方法:synchronized 块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、 synchronized 块之外的代码是可以被多个线程同时访问到的。

  1. 例如:
    synchronized void f() { /* … / }
    synchronized void g() { /
    … */ }
    如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。调用synchronized方法时,对象就会被锁定。

public class MyStack {
int idx = 0;
char [] data = new char[ 6];
public synchronized void push( char c) {
data[ idx] = c;
idx++;
}
public synchronized char pop() {
idx--;
return data[ idx];
}
  1. 说明:
    • 当synchronized方法执行完或发生异常时,会自动释放锁。
    • 被synchronized保护的数据应该是私有(private)的。

  2. 同步的线程状态图:在这里插入图片描述

  3. 线程间的相互作用:
    • wait and notify
    • The pools:
    – Wait pool
    – Lock pool

  4. 具有wait()和notify()的线程状态图:
    在这里插入图片描述

  5. 生产者和消费者问题。

  6. 死锁(deadlock)

  7. wait 与 notify 方法都是定义在 Object 类中,而且是 final 的,因此会被所有的 Java类所继承并且无法重写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在 synchronized 方法或块当中。 当线程执行了 wait方法时,它会释放掉对象的锁。

  8. 另一个会导致线程暂停的方法就是 Thread 类的 sleep 方法,它会导致线程睡眠指定的毫秒数, 但线程在睡眠的过程中是不会释放掉对象的锁的

  9. 例子 一个类里有1个整形变量,一个方法+1,一个方法-1,一个线程+1一个线程-1,声明2个+线程2个-线程,最后实现0和1的交替,我们通过这个例子,来熟悉wait和notify方法。

package JavaBase.multithread;

public class Sample {
    private int number;

    public synchronized void increase(){
        //我用while而不用if,主要是因为,我们再wait的过程中不知道其他程序会进行什么操作。
        while (0 != number) {
            try {
                //进入wait就会释放锁,别的线程就可以获取到对象的锁了
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println(number);
        //随机调用正在wait的程序
        notify();
    }

    public synchronized void decrease(){
        while (0 == number){
            try {
                wait();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println(number);
        notify();
    }

}

增加1

package JavaBase.multithread;

public class IncreaseThread extends Thread{
    private Sample sample;

    public IncreaseThread(Sample sample) {
        this.sample = sample;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000) );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sample.increase();
        }
    }
}

减少1

package JavaBase.multithread;

public class DecreaseThread extends Thread{
    private Sample sample;

    public DecreaseThread(Sample sample) {
        this.sample = sample;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep((long)(Math.random() * 1000) );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sample.decrease();
        }
    }
}

package JavaBase.multithread;

public class MainTest {
    public static void main(String[] args) {
        Sample sample = new Sample();

        Thread thread1 = new IncreaseThread(sample);
        Thread thread2 = new DecreaseThread(sample);

        Thread thread3 = new IncreaseThread(sample);
        Thread thread4 = new DecreaseThread(sample);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

结果是:
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0

线程组

所有线程都隶属于一个线程组。那可以是一个默认线程组,亦可是一个创建线程时明确指定的组。
说明:
– 在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组。
– 若创建多个线程而不指定一个组,它们就会与创建它的线程属于同一个组。

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值