多线程教程+简单实现案例

总结内容

1. 进程是什么,线程又是什么

进程就是正在运行的程序,是操作系统运行程序是产生的,它是独立存在的,进程之间互不干扰。
线程是进程的一个子集,是进程中的实际运作单位,开发人员可以通过操作线程进行多处理器编程。

2. 进程和线程的区别

  • 根本区别:
    进程是操作系统分配资源的基本单位;
    线程是CPU调度和执行的基本单位;
  • 其他区别:
    操作系统中可以同时运行多个进程,一个进程又可以执行多个线程。系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系
    统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

3. 为什么要学习多线程

1)在耗时的操作时使用线程,可以提高程序运行的速度
2)在并行操作时使用线程,提高并行的速度
3)多CPU系统中使用线程,提高CPU利用率
4)改善程序结构,对一个程序进行拆分,降低程序执行难度
5)进程之间的通信十分不方便,同一个进程中的线程可以进行数据共享

4. 实现多线程的方式

在 java 中实现线程的方式有 2 种,一种是继承 Thread,一种是实现 Runnable 接口。

  • 通过继承Thread实现:
    继承 Thread 实现多线程,必须重写 run 方法。
    继承语法:
class MyThread extends Thread{  
    private int ticket = 5;  
    public void run(){  
        for (int i=0;i<10;i++)  
        {  
            if(ticket > 0){  
                System.out.println("ticket = " + ticket--);  
            }  
        }  
    }  
}  

public class ThreadDemo{  
    public static void main(String[] args){  
        new MyThread().start();  
        new MyThread().start();  
        new MyThread().start();  
    }  
} 

运行结果:

ticket = 5
ticket = 4
ticket = 5
ticket = 5
ticket = 4
ticket = 3
ticket = 2
ticket = 1
ticket = 4
ticket = 3
ticket = 3
ticket = 2
ticket = 1
ticket = 2
ticket = 1

每个线程单独卖了5张票,即独立的完成了买票的任务,但实际应用中,比如火车站售票,需要多个线程去共同完成任务,在本例中,即多个线程共同买5张票。

  • 通过实现Runnable:
    实现 Runnable 接口,必须重写 run 方法。
    实现语法:
class MyThread implements Runnable{  
    private int ticket = 5;  
    public void run(){  
        for (int i=0;i<10;i++)  
        {  
            if(ticket > 0){  
                System.out.println("ticket = " + ticket--);  
            }  
        }  
    }  
}  
  
public class RunnableDemo{  
    public static void main(String[] args){  
        MyThread my = new MyThread();  
        new Thread(my).start();  
        new Thread(my).start();  
        new Thread(my).start();  
    }  
} 

运行结果:

ticket = 5
ticket = 2
ticket = 1
ticket = 3
ticket = 4

在第二种方法(Runnable接口实现多线程)中,买票输出的顺序并不是54321,这是因为线程执行时会被抢占资源,所以输出结果不唯一。ticket并不是原子操作。

  • 总结:
    在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入

5. 两种实现多线程方法的区别

  • 区别一:
    Runnable是使用实现接口的方式来实现多线程的,Thread是通过继承的方式来实现多线程的
    Runnable实现接口的方式更好,推荐使用,主要原因是因为实现接口的方式有利于解耦,方便代码的维护工作

  • 区别二:
    Java只支持“接口”的多继承,不支持“类“”的多继承;而继承在java中具有单根性,子类只能继承一个父类,一句话就是单继承多实现

  • 区别三:
    Runnable增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的

  • 区别四:
    Runnable适合多个相同程序的线程区处理同一资源的情况

6. 多线程的同步(线程锁机制)

  • 原子性操作概念:
    如果希望一系列操作(在代码中可以认为是很多句语句),要么都执行,要么都不执行,我们把这种操作叫做原子性操作。
  • 同步方式
    (1)、通过synchronized关键字同步对象的方式实现(synchronized(同步对象){ // 需要同步的代码 })
public class SynchronizedDemo implements Runnable {
    private String name;
    static Integer count = 20;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public static Integer getCount() {
        return count;
    }
    public static void setCount(Integer count) {
        SynchronizedDemo.count = count;
    }
    public SynchronizedDemo() {
    }
    public SynchronizedDemo(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // synchronized关键字里面必须具备唯一性
            // 这里由于是对同一个对象开启多线程,所以this是唯一的
            synchronized (this) {
                if (count > 0) {
                    count--;
                    System.out.println(Thread.currentThread() + " 开始吃苹果, 还剩" + count + "个苹果");
                }
            }
        }
    }
}

public Test {
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(sd, "光头强").start();
        new Thread(sd, "熊大").start();
        new Thread(sd, "熊二").start();

    }
}

运行结果:

"C:\Program Files\Java\jdk-11.0.9\bin\java.exe"
Thread[光头强,5,main] 开始吃苹果, 还剩4个苹果
Thread[熊二,5,main] 开始吃苹果, 还剩3个苹果
Thread[熊大,5,main] 开始吃苹果, 还剩2个苹果
Thread[熊大,5,main] 开始吃苹果, 还剩1个苹果
Thread[熊二,5,main] 开始吃苹果, 还剩0个苹果
  • 通过同步方法实现(synchronized 方法返回类型方法名(参数列表){// 其他代码})
public class SynchronizedDemo implements Runnable {
    private String name;
    static Integer count = 5;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static Integer getCount() {
        return count;
    }

    public static void setCount(Integer count) {
        SynchronizedDemo.count = count;
    }

    public SynchronizedDemo() {
    }

    public SynchronizedDemo(String name) {
        this.name = name;
    }

    @Override
    public synchronized void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count > 0) {
                count--;
                System.out.println(Thread.currentThread() + " 开始吃苹果, 还剩" + count + "个苹果");
            }
        }
    }
}

public Test {
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(sd, "光头强").start();
        new Thread(sd, "熊大").start();
        new Thread(sd, "熊二").start();

    }
}

运行结果:

"C:\Program Files\Java\jdk-11.0.9\bin\java.exe"
Thread[光头强,5,main] 开始吃苹果, 还剩4个苹果
Thread[熊二,5,main] 开始吃苹果, 还剩3个苹果
Thread[熊大,5,main] 开始吃苹果, 还剩2个苹果
Thread[熊大,5,main] 开始吃苹果, 还剩1个苹果
Thread[熊二,5,main] 开始吃苹果, 还剩0个苹果

7、多线程常用方法

(1)wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。

(3)notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

(4)Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

8、多线程综合案例

综合案例代码

总结

以上就是对多线程的总结了,代码仅供参考,欢迎讨论交流。

posted @ 2021-04-07 14:14  Yan_Yang  阅读(132)  评论(0编辑  收藏  举报