Java复习笔记 第十章多线程

目录

一 、线程的概念

二、多线程需求

三、线程的创建与启动

Thread类创建线程方法

Runnable类创建多线程(使用了代理模式)

线程启动补充

多线程售票案列模拟

四 、线程状态和方法

线程状态

常用方法 

五 、线程同步

同步代码块

同步方法

同步锁

六 、死锁

一 、线程的概念

程序:是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。比如QQ程序、

进程:是程序的一次执行过程,或是正在运行的一个程序。每个程序都有一个独立运行的进程,进程之间相互独立,互不影响。进程执行是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期 如:运行中的QQ,运行中的MP3播放器 程序是静态的,进程是动态的 。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

Java复习笔记 第十章多线程_第1张图片

 

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事、同时下载多首歌曲

并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

线程:进程想要执行任务就需要启动线程,线程是进程的最小执行单位,是一个程序内部的一条执行路径。

如果一个进程的任务是串行执行的,此时就需要一个进程就可以,当任务A执行完毕后,接着执行任务B。如果一个进程的任务是并行执行时,此时需要多个进程同时执行,这里就是“并行”。

Java复习笔记 第十章多线程_第2张图片

 

 Java复习笔记 第十章多线程_第3张图片

 

若一个进程同一时间并行执行多个线程,就是支持多线程的

线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小 一个进程中的多个线程共享相同的内存单元/内存地址空间

它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

二、多线程需求

程序需要执行两个或者多个任务同时执行。比如使用QQ音乐下载音乐和播放音乐同时进行

程序需要一些等待操作。用户输入、文件读写、搜索时。

后台程序运行。

三、线程的创建与启动

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。

Thread类的特性

每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体

通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

  • Thread类创建线程方法

  1. 创建一个继承于Thread的类
  2. 重写run()方法,实现具体代码逻辑
  3. 创建Thread类的子类的对象
  4. 调用start()方法启动线程,不要用run方法 
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2. 重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //3. 创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        t1.start();
        //问题一:我们不能通过直接调用run()的方式启动线程。
//        t1.run();

        //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
//        t1.start();
        //我们需要重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍然是在main线程中执行的。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }

}

Java复习笔记 第十章多线程_第4张图片

  •   Runnable类创建多线程(使用了代理模式)

    public class Thread02 {
        public static void main(String[] args) {
            Dog dog = new Dog();
            //dog.start(); 这里不能调用start
            //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
            Thread thread = new Thread(dog);
            thread.start();
    
    //        Tiger tiger = new Tiger();//实现了 Runnable
    //        ThreadProxy threadProxy = new ThreadProxy(tiger);
    //        threadProxy.start();
        }
    }
    
    class Animal {
    }
    
    class Tiger extends Animal implements Runnable {
    
        @Override
        public void run() {
            System.out.println("老虎嗷嗷叫....");
        }
    }
    
    //线程代理类 , 模拟了一个极简的Thread类
    class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
    
        private Runnable target = null;//属性,类型是 Runnable
    
        @Override
        public void run() {
            if (target != null) {
                target.run();//动态绑定(运行类型Tiger)
            }
        }
    
        public ThreadProxy(Runnable target) {
            this.target = target;
        }
    
        public void start() {
            start0();//这个方法时真正实现多线程方法
        }
    
        public void start0() {
            run();
        }
    }
    
    
    class Dog implements Runnable { //通过实现Runnable接口,开发线程
    
        int count = 0;
    
        @Override
        public void run() { //普通方法
            while (true) {
                System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
    
                //休眠1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if (count == 10) {
                    break;
                }
            }
        }
    }
    

  • 线程启动补充

  1. 调用start()方法,该方法内部中还会调用start0()方法,它才是启动线程的方法,由于start0()方法是一个本地方法,是用C/C++实现的。该方法是由JVM虚拟机运行的。
  2. 线程启动后不一定立马执行,只是将线程变成可运行状态,要等待CPU的调度,run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定
  3. 真正实现多线程的是start0而不是run方法
  4. 运行分线程时为什么主线程还在调用。对于在主线程调用分线程后,此时程序中有两个线程,线程的执行等待CPU的调度,即使主线程此时执行结束后死亡后,分线程依然要进行到结束。此时整个进程才算结束。
  5. 对于共享资源的使用,尽量使用实现Runnable接口的线程、或者Collable接口的线程,而不是继承Thread的线程

Java复习笔记 第十章多线程_第5张图片

  • 多线程售票案列模拟

    public class SellTicket {
        public static void main(String[] args) {
    
            //测试
    //        SellTicket01 sellTicket01 = new SellTicket01();
    //        SellTicket01 sellTicket02 = new SellTicket01();
    //        SellTicket01 sellTicket03 = new SellTicket01();
    //
    //        //这里我们会出现超卖..
    //        sellTicket01.start();//启动售票线程
    //        sellTicket02.start();//启动售票线程
    //        sellTicket03.start();//启动售票线程
    
    
            System.out.println("===使用实现接口方式来售票=====");
            SellTicket02 sellTicket02 = new SellTicket02();
    
            new Thread(sellTicket02).start();//第1个线程-窗口
            new Thread(sellTicket02).start();//第2个线程-窗口
            new Thread(sellTicket02).start();//第3个线程-窗口
    
    
        }
    }
    
    
    //使用Thread方式
    
    class SellTicket01 extends Thread {
    
        private static int ticketNum = 100;//让多个线程共享 ticketNum
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    break;
                }
    
                //休眠50毫秒, 模拟
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));
    
            }
        }
    }
    
    
    
    //实现接口方式
    class SellTicket02 implements Runnable {
        private int ticketNum = 100;//让多个线程共享 ticketNum
    
        @Override
        public void run() {
            while (true) {
    
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    break;
                }
    
                //休眠50毫秒, 模拟
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
    
            }
        }
    }

由该售票案列,可以引出线程安全,如果多个线程共享一个资源时,在多线程时,会出现线程安全问题,比如上述案列,可能卖票的票号为负数,现实则是不存在的。于是乎就需要解决方案来保证共享资源在同一时刻只能有一个线程来进行进行访问。

四 、线程状态和方法

  • 线程状态

官方给出了六种状态,可运行状态可以细分为就绪和运行两种状态

Java复习笔记 第十章多线程_第6张图片

 Java复习笔记 第十章多线程_第7张图片

  •  NEW(新建状态)

    新建一个线程对象时,该线程对象就处于新建状态,不能够运行,和其他对象一样,仅仅只由JVM虚拟机分配了内存,没有线程的动态特征。

  • RUNNABLE (可运行状态)

    当线程对象创建了start()方法,此时线程就进入了可运行状态,该状态内部可以再细化分为就绪状态和运行状态,线程可以在这两个状态之间相互转换。

  • BLOCKED (阻塞状态)

    处于运行状态的线程可能会丧失CPU的执行权,进入阻塞状态。直到线程重新进入就绪状态,才有可能进入运行状态。阻塞状态的线程只有就绪才能运行。

1.获取同步锁时,此时该锁被其他线程占用。就会进入到阻塞状态 

2. 线程运行时,获取IO请求时。会进入到阻塞状态

  • WAITING (等待状态)

如果线程调用wait()、join()方法,则进入等待线程,进入等待状态的线程不能立即争夺CPU的执行权,必须等其他线程完成特定动作后,才能重新争夺CPU的执行权.

调用了wait()方法,需要notify()、notifyAll()方法来唤醒,调用join()需要等待加入的线程执行完毕.

  • TIMED_WAITING (定时等待状态)

调用了sleep()方法等,设置等待时间,等待时间过后才能继续争夺CPU的使用权。进入运行状态

  • TERMINATED (终止状态)

线程正常执行完毕后或者抛出异常或错误,线程就进入终止状态,生命周期就此结束,不会再进入到其他状态

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + " 状态 " + t.getState());
        t.start();

        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + " 状态 " + t.getState());
            Thread.sleep(500);
        }

        System.out.println(t.getName() + " 状态 " + t.getState());

    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}
  • 常用方法 

setPriority()方法设置线程的优先级,更有可能获取到CPU的执行权,但并不意味着CPU一定优先执行优先级高的线程

Thread.sleep() 线程休眠,让正在运行的线程暂停,此时让出CPU的执行权.等到休眠时间结束后,才有机会获取到CPU的执行权

Thread.Current().setName() 设置线程名字

yield() 线程让步,使线程由运行状态变成就绪状态。不会使线程进入到阻塞或休眠状态下。强调一下,让步之后所有线程都会重新抢夺CPU的执行权,具体哪一个线程获取CPU使用权,需要调度器分配

.join() 线程插队 插队会让线程进入等待状态。之后等待插队的线程执行完毕,才能进入可运行状态,抢夺CPU执行权

设置守护线程 setDaemon(),如果希望主线程结束后,分线程也结束,就可以设置守护线程 设置后台线程,必须设置在线程启动之前才行

其他方法可以查看JavaAPI

五 、线程同步

  • 同步代码块

同步代码块需要一把互斥锁,该互斥锁可以由任何对象构建锁,对于用如果一个线程A拿到后,其他线程则不能获取该锁,进入排队阻塞状态,只有等到线程A执行完毕后,把锁释放了。其他线程才能进入获取敏感数据的使用(这里还是模拟多线程售票)

同步代码块也可以写在方法中,对于普通方法,则使用的锁则是this,对象本身。对于静态方法则是类本身(这里和反射内容有关)

如果不写在方法中,就是同步代码块。该互斥锁可以是任意的对象。

在继承Thread类实现多线程,多个线程需要使用同一个对象锁则需要static修饰

当某个互斥锁用sychronized修饰时,就表明该对象在任意时刻只能被一个线程访问

class A implements Runnalbe { 
        int ticketNum = 100;
        Object object = new Object(); // 对象锁

   public void sell() {
        synchronized (object) { // 该对象锁是object
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一共同步方法
        }
    }
}
  • 同步方法

     [权限修饰符] synchronized 返回值类型 方法名(形参列表) {
          // 同步方法的互斥锁根据方法来判断
    }
     举列
     public synchronized static void m1() { 
       // 静态方法该锁是类本身SellTicket03.class 
     }
      
     public synchronized void m1() {  
      // 普通方法该锁是创建的该对象this
    }
    
  • 同步锁

    class B implements Runnable {
        private int num = 100;
        private final Lock lock = new ReentrantLock(); // 线程锁
        @Override
        public void run() {
            while(true) {
                lock.lock(); // 线程上锁
                if(num > 0) {
                    try{
                        Thread.sleep(1000);
                        System.out.println("正在售票" + Thread.currentThread().getName() + "第" + num-- + "张票");
                    }catch (Exception e) {
                        e.printStackTrace();
                    }finally {
                        lock.unlock(); // 线程解锁
                    }
                }
                System.out.println("xx");
            }
        }
    }

六 、死锁

死锁的描述可以概括为 A线程占用了B的锁,B线程占用了A的锁。两个线程僵持不动,处于挂起状态,形成了死锁的状况,也可以实现Thread类来实现死锁状况

public class DeadLockTest {
    public static void main(String[] args) {
        A a = new A();
        A a1 = new A();
        a1.loop = false;
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(a1);

        t1.start();
        t2.start();
    }
}
class A implements Runnable{
    boolean loop = true;
   // 模拟死锁
    static Object o1 = new Object();
    static Object o2 = new Object();
    @Override
    public void run() {
       if(loop) {
           synchronized (o1) {
               System.out.println("使用了o1锁,需要获取o2锁");
               synchronized (o2) {
                   System.out.println("使用o2锁成功");
               }
           }
       } else {
           synchronized (o2) {
               System.out.println("使用了o2锁, 需要获取o1锁");
               synchronized (o1) {
                   System.out.println("获取o1成功");
               }
           }
       }
    }
}

你可能感兴趣的:(Java,java)