2020.5.31

多线程

  1. 什么是单线程,代码的执行路径只有一条路径。
  2. 缺点:有可能某个代码耗时时间长需要等待,其他程序不能进行,用户感觉差。
  3. 多线程:代码的执行路径有多条,这种执行环境称之为多线程环境。一般在支线程运行耗时代码。

线程的概述

  1. 线程依赖进程,没有进程也就谈不上线程。
  2. 进程开启之后就会执行很多任务,每个任务就是一个线程。
  3. 线程是CPU调度的基本单位。
  4. 我们Java编程,就是线程编写,因为虚拟机就是进程,不能在虚拟机里面运行进程。只能在虚拟机里面运行线程。
  5. 意义:多线程提高对CPU的使用率。
  6. 线程的调度是随机性的,没有规律。

进程

  1. 进程就是正在运行的应用程序。
  2. 计算机在同一时间点上,多个进程不是同时进行。同一个时间点上只能执行一个进程。感觉同时进行,因为(单核)CPU是在多个进程中高速切换,人的感官不会感觉出来。
  3. 开启一个进程,就是拥有资源。

并发和并行

  1. 并发:指的时多个任务,高速的交替执行。同一个实体上的多个事件。多台处理器上同时处理多个任务
  2. 并行:多个任务在同一时刻同时执行,不同实体上的多个事件。一台处理器上同时处理多个任务

java运行原理

  1. Java程序依靠虚拟机运行,虚拟机是一个进程,我们开启一个进程后,虚拟机就会调用主线程,所以我们编写的是多线程程序,Java是不能直接调用系统功能的,我们没法直接实现多线程,但是Java虚拟机可以直接调用C/C++写好的程序调用多线程,
  2. 因为CPU调度的基本单位是线程,在运行多线程程序时,单核CPU执行线程的方式是

怎么创建线程(线程开启的第一种方式)

  1. 我们可以通过Java提供的Thread类创建线程,和开启线程。
  2. 线程不要重复开启,重复开启会抛出异常。
  • 线程调用start方法开启线程
  • run()方法里面封装的是线程要执行的逻辑。
  1. 可以给线程起名和获取名字。
  2. 获取主线程的名字和修改名字。
  3. 多个线程就是并发执行。
  4. 多个线程的优先级设置。一般不设置,理论上说有效果。优先级高的抢占的概率高一些。
  • 线程默认优先级是:5
  • 线程的优先级最大是10
  • 线程的优先级最小是1

线程的第二种开启方式

  1. 实现Runable接口,重写run方法,如果一个类继承了另一个类,要想创建多线程,就不能继承Thread类。Java提供了一个接口,实现了此接口就可以创建线程。

线程开启的第三种方法

  1. 实现Callable接口,重写call方法,执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
  2. 特点是:当子线程执行结束之后,可以返回线程执行的结果。
  3. 创建的步骤:
  • 创建一个类实现Callable 接口 重写接口中的call方法
  • 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
  • 创建Thread类, 将FutureTask对象作为参数传进去

Runable/Callable的差别

  1. Runable:没有返回值,不能抛出异常,只能捕获处理
  2. Callable:有返回值,可以抛出异常。

代码

1.package org.westos.demo1; 
 
/*Author:LH
CreatTime:2020.05.31.15:13*/

public class Test1 {
    public static void main(String[] args) {
        System.out.println("主线程前面代码");
        System.out.println("主线程前面代码");
        System.out.println("主线程前面代码");
        MyThread myThread = new MyThread();
//        开启支线程,是创建支线程的实例对象并调用start()方法。
//        注意这里不是调run()方法,如果是调用此方法,就是普通的main()方法调用,还是主线程。
        myThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("A");
        }
//        同一个支线程的实例对象不能被开启两次。否则会报错。
        MyThread myThread1 = new MyThread();
        myThread1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程后面代码");
        }


    }
}
//创建分支线程,让一个类继承Thread,并重写run方法。run方法体内的代码就是支线程
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("B");
        }
    }
}
2.package org.westos.demo2; 
 
/*Author:LH
CreatTime:2020.06.05.15:44*/

public class Test2 {

    public static void main(String[] args) {
        Mythread th1 = new Mythread("窗口1");
        Mythread th2 = new Mythread("窗口2");
        Mythread th3 = new Mythread("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class Mythread extends Thread {
//    重写构造方法,将线程名称传入有参构造。
    public Mythread(String name) {
        super(name);
    }
    //将共享的数据变成静态变量。所有线程共同操作买100张票
    static int num = 100;
    @Override
    public void run() {
        while (num > 0) {
//            获取当前线程的名称
            System.out.println(this.getName()+"卖出第" + (num--) + "张票");
        }
    }
}
3.package org.westos.demo4; 
 
/*Author:LH
CreatTime:2020.06.05.16:38*/

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        创建MyCallable对象
        MyCallable myCallable = new MyCallable();
//        实例化 FutureTask类,并将MyCallable对象作为参数传进去。
        FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
//        创建 Thread类,并将FutureTask类的子类对象传进去
        Thread th1 = new Thread(futureTask);
        th1.start();
//        获取线程执行的结果
        Integer integer = futureTask.get();
        System.out.println(integer);
        System.out.println("主线程");
        System.out.println("主线程");
        System.out.println("主线程");
        System.out.println("主线程");
    }
}
class MyCallable implements Callable{

    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}

线程休眠

  1. 线程在开始之后调用sleep()方法,可以使线程休眠。可以用于阻塞线程
package org.westos.demo2; 
 
/*Author:LH
CreatTime:2020.06.05.14:38*/

public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程进来了");
//        获取主线程的对象
        Thread thread = Thread.currentThread();
//        设置主线程名称
        thread.setName("主线程");
//        设置主线程休眠时间
        thread.sleep(2000);
        System.out.println(thread.getName());
//        第二中方法创建线程
        MyRunable runable = new MyRunable();
        Thread th1 = new Thread(runable);
//        设置线程名称
        th1.setName("线程1");
        th1.start();
//        th1.sleep(1000);    //设置线程1启动后休眠1秒,在线程启动后设置
        Thread th2 = new Thread(runable);
        th2.setName("线程2");
//        设置线程为守护线程,伴随着主线程死亡而结束。
        th2.setDaemon(true);
        th2.start();


        System.out.println("主线程");
        System.out.println("主线程");
        System.out.println("主线程");

    }
}
//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            Thread thread = Thread.currentThread();
            String name = thread.getName();
//            判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
            if (name.equals("线程1")){
                try {
                    thread.sleep(100);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程名字"+name+"线程方法");
        }
    }
}

加入线程

  1. join();可以让多个线程从并发执行变成串行。
  2. 这个方法要在线程启动之后调。
package org.westos.demo2; 
 
/*Author:LH
CreatTime:2020.06.05.16:04*/

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        numThread th1 = new numThread("th1");
        numThread th2 = new numThread("th2");
        numThread th3 = new numThread("th3");
//        System.out.println("主线程执行");
        th1.start();
//        th1开启了加入了串行执行,所以先执行th1之后再执行System.out.println("主线程执行");
        th1.join();
        th2.start();
//        th2和th3不会按照串行执行,
        th3.start();
        System.out.println("主线程执行");
    }
}

class numThread extends Thread {

    public numThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(this.getName() + "分支线程执行l");
        }
    }
}

守护线程

  1. 主线程(用户线程)循环结束时,守护线程也要结束。
  2. 守护线程设置在线程开始前,setDaemon(true).
package org.westos.demo2; 
 
/*Author:LH
CreatTime:2020.06.05.14:38*/

public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程进来了");
//        获取主线程的对象
        Thread thread = Thread.currentThread();
//        设置主线程名称
        thread.setName("主线程");
//        设置主线程休眠时间
        thread.sleep(2000);
        System.out.println(thread.getName());
//        第二中方法创建线程
        MyRunable runable = new MyRunable();
        Thread th1 = new Thread(runable);
//        设置线程名称
        th1.setName("线程1");
        th1.start();
//        th1.sleep(1000);    //设置线程1启动后休眠1秒,在线程启动后设置
        Thread th2 = new Thread(runable);
        th2.setName("线程2");
//        设置线程为守护线程,伴随着主线程死亡而结束。
        th2.setDaemon(true);
        th2.start();


        System.out.println("主线程");
        System.out.println("主线程");
        System.out.println("主线程");

    }
}
//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            Thread thread = Thread.currentThread();
            String name = thread.getName();
//            判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
            if (name.equals("线程1")){
                try {
                    thread.sleep(100);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程名字"+name+"线程方法");
        }
    }
}

清除阻塞状态/让线程死亡/线程礼让

  1. interrupt():清除阻塞状态。线程在sleep()和wait()时都是阻塞状态。
  2. stop():让线程死亡。
  3. yield():线程礼让,就是暂停当前的执行对象,执行其他线程。但是由于礼让的时间短暂,当礼让时,其他线程没有抢到执行权,那么当前线程就会重新和另一个线程抢执行权。所以礼让效果不是很明显(理论上是你执行一次,我执行一次)
package org.westos.demo3;
 
/*Author:LH
CreatTime:2020.06.05.14:38*/

public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程进来了");
//        获取主线程的对象
        Thread thread = Thread.currentThread();
//        设置主线程名称
        thread.setName("主线程");
//        设置主线程休眠时间
        thread.sleep(2000);
        System.out.println("主线程名称:"+thread.getName());
//        第二中方法创建线程
        MyRunable runable = new MyRunable();
        Thread th1 = new Thread(runable);
//        设置线程名称
        th1.setName("线程1");
        th1.start();
//        线程休眠也是一种阻塞状态,这里可以使用interrupt清除阻塞状态。
        th1.interrupt();
//        th1.sleep(1000);    //设置线程1启动后休眠1秒,在线程启动后设置
        Thread th2 = new Thread(runable);
        th2.setName("线程2");
//        设置线程为守护线程,伴随着主线程死亡而结束。
        th2.setDaemon(true);
        th2.start();
//        让主线程死亡,下面的程序就不能执行了
        thread.stop();
        System.out.println("主线程");
        System.out.println("主线程");
        System.out.println("主线程");

    }
}

//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            Thread thread = Thread.currentThread();
            String name = thread.getName();
//            判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
            if (name.equals("线程1")){
                try {
                    thread.sleep(100);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程名字:"+name+"  线程方法");
        }
    }
}

//线程礼让
package homework.demo1;

class Test extends Thread {
	public static void main(String[] args) {
		Test t1 = new Test();
		Test t2 = new Test();
		t1.start();
		t2.start();
	}
	public void run() {
		System.out.println("1");
//		线程礼让
		yield();
		System.out.println("2");
		System.out.println("3");
		System.out.println("4");
		System.out.println("5");
		System.out.println("6");
	}
}
//结果:F:\ruanjian\JDK\jdk-13.0.2\bin\java.exe "-javaagent:F:\ruanjian\IEDA\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar=58283:F:\ruanjian\IEDA\IntelliJ IDEA 2020.1.1\bin" -Dfile.encoding=UTF-8 -classpath F:\untitled\2020.5.31-多线程\out\production\2020.5.31-多线程 homework.demo1.Test
1
1
2
3
4
5
6
2
3
4
5
6



卖票程序

package org.westos.demo5;
 
/*Author:LH
CreatTime:2020.06.05.15:44*/

public class Test2 {
//三个窗口共同卖100张票
    public static void main(String[] args) {
        Mythread th1 = new Mythread("窗口1");
        Mythread th2 = new Mythread("窗口2");
        Mythread th3 = new Mythread("窗口3");
        th1.start();
        th2.start();
        th3.start();

    }
}

class Mythread extends Thread {
//    重写构造方法,将线程名称传入有参构造。
    public Mythread(String name) {
        super(name);
    }
    //将共享的数据变成静态变量。所有线程共同操作买100张票
    static int num = 100;
    @Override
    public void run() {
        while (num > 0) {
//            获取当前线程的名称
            Thread thread = currentThread();
            try {
                thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getName()+"卖出第" + (num--) + "张票");
        }
    }
}

买票程序,模拟网络延迟

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eowCWHeI-1591348787323)(https://pic.downk.cc/item/5eda0a15c2a9a83be5302087.jpg)]

  1. 多线程环境下对共享数据操作出现了线程安全问题,票数出现了负数等错误数据。

@Override
public void run() {
while (num > 0) {
// 获取当前线程的名称
Thread thread = currentThread();
try {
thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+“卖出第” + (num–) + “张票”);
}
}
}

### 买票程序,模拟网络延迟

[外链图片转存中...(img-eowCWHeI-1591348787323)]

1. 多线程环境下对共享数据操作出现了线程安全问题,票数出现了负数等错误数据。