Tips:样式蚂蚁森林浇水get

java进阶——day05-2 异常、线程

多线程

  学习程序在没有跳转语句的前提下,都是由上至下依次执行,那么现在想要设计一个程序,边听歌边打游戏,如何设计?

并发与并行

  并发:指两个或多个事件在同一时段内发生。

  并行:指两个或多个事件在同一时刻发生(同时发生)

 

 

 线程与进程

  1、进程:

  指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;

  进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序既是一个进程创建--运行--消亡的过程

 

 

   2、线程:

  线程进程的一个执行单元负责当前进程中程序的执行,一个进程至少有一个线程。

  一个进程可以有多个线程的,这个应用也可以称之为多线程程序。

  总而言之一个程序运行后,至少一个进程,一个进程可以包含多个线程

 

 

 线程调度

  1、分时调度

  所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间

  2、抢占式调度

  优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,Java使用的为抢占式线程

  设置线程优先级

  

 

 

   3、注意

   多线程程序并不能提高程序运行的速度,但能提高程序运行效率,让CPU使用率更高。

创建线程类

主线程(main单线程)

  单线程,从上到下依次执行

package day06;

public class Person {
    private String name;

    //自定义run方法
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(this.name+"---》"+i);
        }
    }
    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

public class demo01 {
    //主线程演示
    public static void main(String[] args) {
        //创建Person对象
        Person p1 = new Person("大黄");
        p1.run();

        Person p2 = new Person("旺财");
        p2.run();
        //执行和输出都是从上往下
    }
}
main

 

 

   主线程的缺陷:

  当发生异常时,会中断程序,不再执行后续代码

创建线程类---第一种方法

  1、简述:

  java.lang.Thread类:是描述线程的类,我们想要实现多线程,就必须继承Thread类

  2、实现步骤

  1.创建一个Thread类的子类,继承Thread类

  2.在Thread类的子类中,重写Thread类中的run方法,设置线程任务(开启线程做什么?)

  3.创建Thread类的子类对象

  4.调用Thread类中的方法start方法,开启新的线程,执行run方法

  void start() 使该线程开始执行;Java虚拟机调用该线程run方法。

  3、注意事项

  1.start()运行结果是两个线程并发地运行

    当前线程(main主线程)

    另一个线程(创建的新线程,执行run方法)

  2.多次启用一个线程是非法的。特别是当线程已经结束执行,不能重新启动。

package day06.day06_1Thread;

public class ThreadSub extends Thread {
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread.run--->"+i);
        }
    }

}
Thread
package day06.day06_1Thread;

import day06.Person;

public class demo01 {
    public static void main(String[] args) {
        //创建多线程子类对象
        ThreadSub p1 = new ThreadSub();
        //启用主线程
        p1.start();

        //创建当前线程
        for (int i = 0; i < 5; i++) {
            System.out.println("main--->"+i);
        }
    }
}
main

  主线程和Thread线程同时执行

多线程原理

  例如:

package day06.day06_1Thread;

public class ThreadSub extends Thread {
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread.run--->"+i);
        }
    }

}
Thread子类对象
package day06.day06_1Thread;

import day06.Person;

public class demo01 {
    public static void main(String[] args) {
        //创建多线程子类对象
        ThreadSub p1 = new ThreadSub();
        //启用主线程
        p1.start();

        //创建当前线程
        for (int i = 0; i < 5; i++) {
            System.out.println("main--->"+i);
        }
    }
}
main

  流程图

  

 

 

  1、程序启动main时,Java虚拟机启动一个进程,主线程main在main()调用时被创建。

  2、随机调用start()方法,新线程启动,这样整个应用就在多线程下运行。

  多线程为什么能并发执行呢?

  多程序执行时,在栈内存中,每一个执行线程都有一片属于自己的栈内存空间进行方法的压栈和弹栈

  当线程执行完毕,线程自动在栈内存中释放。当所有线程执行结束,进程也就结束了

 Thread类

常用方法

 

  获取当前线程名称:

  1、使用Thread类中的getName();

    String getName()返回该线程名称

package day06.day06_1Thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        //获取当前线程名称
        String name = getName();
        System.out.println(name);
    }
}
MyThread
package day06.day06_1Thread;

public class demo02 {
    public static void main(String[] args) {
        //创建Thread子类对象
        MyThread mt = new MyThread();
        //调用start方法 开启新线程 执行run方法
        mt.start();//Thread-0

        MyThread mt1 = new MyThread();
        mt1.start();//Thread-1

        MyThread mt3 = new MyThread();
        System.out.println(mt3.getName());//Thread-2
    }
}
main

  2、可以先获取当前正在执行的线程,使用线程中的getName(),获取线程名称。

    static Thread currentThread() 返回当前正在执行线程对象的引用

package day06.day06_1Thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        //获取当前线程名称
        String name = getName();
        System.out.println(name);
    }
}
MyThread
package day06.day06_1Thread;

public class demo02 {
    public static void main(String[] args) {
        //创建Thread子类对象
        MyThread mt = new MyThread();
        //调用start方法 开启新线程 执行run方法
        mt.start();//Thread-0

        //获取当前正在执行的线程
        Thread t = Thread.currentThread();
        System.out.println(t);//Thread[main,5,main]
    }
}
Thred.currentThread()

  3、链式编程Thread.currentThread.getName();

package day06.day06_1Thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        //获取当前线程名称
//        String name = getName();
//        System.out.println(name);
        //链式编程
        System.out.println(Thread.currentThread().getName());//main

    }
}
Thread子类
package day06.day06_1Thread;

public class demo02 {
    public static void main(String[] args) {
        //创建Thread子类对象
        MyThread mt = new MyThread();
        mt.start();//Thread-0
        System.out.println(Thread.currentThread().getName());//main
    }
}
main

  public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  public void run() 此线程要执行的任务在此处定义代码。
  public static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

package day06.day06_1Thread;

public class demo03 {
    /**
     * 使当前正在执行的程序以指定的毫秒数进行"睡眠"---暂停执行
     * 毫秒结束后 继续执行
     */
    public static void main(String[] args) {
        //模拟钟表 一秒打印一次
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);
            try {
                //睡眠 1 秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
Thread.sleep() 

创建线程----方式二

  采用Java.lang.Runnable也是其一,我们只需要重写run方法即可

  步骤:

  1、定义Runnable的实现类,并重写Runnable中的run方法

  2、创建Runnable实现类对象

  3、将对象作为Thread的target创建Thread对象

  4、调用线程对象start()方法

package day06.day06_1Thread;
//创建Runnable实现类
public class RunnableImpl implements Runnable{
    //重写Runnable中的run方法
    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
Runnable实现类
package day06.day06_1Thread;

public class demo04 {
    public static void main(String[] args) {
      //创建Runnable实现类对象
        RunnableImpl runImp = new RunnableImpl();
        //创建Thread类 传递Runnable对象
        Thread t = new Thread(runImp);
        //调用t.start()
        t.start();

        //创建main方法中的线程
        for (int i = 0; i < 4; i++) {
            System.out.println("main"+"--->"+i);
        }
    }
}
mian主线程

Thread和Runnable的区别

  主要区别:与继承类 实现类的区别类似

实现Runnabled接口,创建多线程的好处:

  1、避免了单继承的局限性

  一个类只能继承一个类,类继承了Thread类就不能继承其他的类

  实现Runnable接口,还可以继承其他的类,实现其他的接口

  2、增强了程序的扩展性,降低了程序的耦合性(解耦)

  实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)

  实现类重写了run方法:用来设置新线程任务

  创建Thread类对象,调用start()方法:用来开启新线程。

匿名内部类实现线程的创建

  使用线程匿名内部类的方式,以便于实现每个线程执行不同的任务

  使用匿名内部类实现Runnable接口,重写Runnable接口中的run()方法

package day06.day06_1Thread;
//创建Runnable实现类
public class RunnableImpl implements Runnable{
    //重写Runnable中的run方法
    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
Runnable的实现类
package day06.day06_1Thread;

public class demo05 {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl runImp = new RunnableImpl();
        //创建Thred类对象 将实现类对象 作为target传递
        Thread thread = new Thread(runImp);
        //启动新线程
        thread.start();

        //创建主方法中的线程
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }

        //创建匿名内部类实现Runnable多线程
        Runnable newRun = new Runnable(){
            //重写Runnable中run()
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("Hello"+i);
                }
            }
        };
        //启动内部类线程
        new Thread(newRun).start();
    }
}
main+内部类

线程安全

  如果有多个线程同时运行,而这些线程会同时运行这段代码。程序每次运行结果和单线程一样的。而且其他的变量的值也和预期的是一样的,就是线程安全。

  例如:

  模拟电影院的售票窗口,实现多个窗口售票(多个窗口卖100张票)

package day06.day06_1Thread;

public class Ticket implements Runnable {
    //定义票的数量
    int ticket = 100;
    @Override
    public void run() {
        //每个窗口的买票操作
        //窗口 永远开启
        while (true){
            if(ticket>0){//有票
                //出票操作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖"+ticket--);
            }
        }
    }
}
Runnable实现类
package day06.day06_1Thread;

public class Caname {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //模拟三个窗口
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        //同时售票
        t1.start();
        t2.start();
        t3.start();
    }
}
模拟买票窗口

 

 

   发现:

  1、相同的票数,比如100被售出3次

  2、不存在的票,比如0与-1

  这几种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全

  注意:

  线程安全是不允许产生的

线程同步

  注意事项:

  当我们使用多线程访问同一资源时且多线程中对资源有写的操作,就容易出现线程安全问题

  解决办法:

  要解决上述多线程并发访问同一资源的安全性问题,也就是解决重复票与不存在票的问题,java中提供了同步机制(synchonized)来解决

  根据案例简述:

  窗口1进入操作时,窗口2和窗口3只能在外面等,窗口1操作结束,窗口1、2、3才有机会进入代码去执行。

  也就是说在某个线程修改共享资源时,其他线程不得修改,等等线程修改完毕,才能去抢夺CPU资源,完成操作,保证数据的同步性,解决线程不安全的现象。

  解决详情:

  1、同步代码块

  2、同步方法

  3、锁机制

同步代码块

  1、格式:

  synchronized(同步锁){  

    可能会出现线程安全问题的代码(访问了共享数据的代码)

  }

  2、注意

  1.通过代码块中的锁对象可以使用任意的对象,例如Object....

  2.必须保证多个线程使用同一个同步锁

  3.锁对象作用:

    把同步代码块锁住,只能让一个线程在同步代码块中执行

  3、例如

package day06.day06_2;

public class RunnableImpl implements Runnable {
    //定义票数量
    private int tickets = 100;
    //创建同步锁对象
    Object obj = new Object();
    @Override
    public void run() {
        //使用死循环 重复买票操作
        while (true){
            //同步代码块
            synchronized (obj){
                if(tickets>0){
                    //提高安全性 使用睡眠
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //票存在 卖票
                    System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票");
                    tickets--;
                }
            }
        }
    }
}
Runnable实现类(同步代码块)
package day06.day06_2;

public class demo {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建三个窗口
        Thread T1 = new Thread(run);
        Thread T2 = new Thread(run);
        Thread T3 = new Thread(run);
        //三个窗口同时售票
        T2.start();
        T1.start();
        T3.start();
    }
}
main(多线程出票口)

  4、同步技术的原理

  使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

  例如上述例子中,3个线程,同时抢夺CPU资源谁抢到谁执行run方法进行卖票

  T1抢到cpu执行权--->执行run()--->遇到synchronized代码块--->T1检查代码块是否有锁对象--->发现有--->获取锁对象进入同步代码块--->执行

  T2抢到cpu执行权--->执行run()--->遇到synchronized代码块--->T1检查代码块是否有锁对象--->发现没有--->等待上一线程(T1)归还锁对象--->上一线程(T1)归还锁对象--->获取锁对象进入同步代码块--->执行

  5、注意

  1.同步中的线程,没有执行完毕不会释放对象锁同步外的线程没有锁不会进入同步代码块

  2.因为同步代码块需要反复的--->检查对象锁--->获取同步锁--->归还同步锁 效率会比较低 但是安全

同步方法

  1、同步方法:

  使用synchronized修饰的方法,就叫做同步方法

  2、格式:

  修饰符 synchronized 返回值类型 方法名(){

  可能会产生线程安全问题的代码}

  3、例如:

package day06.day06_3;

public class RunnableImpl implements Runnable {
    //定义票数量
    private int tickets = 100;

    @Override
    public void run() {
        //使用死循环 重复买票操作
        while (true){
            //调用同步方法
            method();
        }
    }
    //创建同步方法
    public synchronized void method(){
        if(tickets>0){
            //提高安全性 使用睡眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在 卖票
            System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票");
            tickets--;
        }
    }
}
Runnable实现类---同步方法
package day06.day06_3;

public class demo {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建三个窗口
        Thread T1 = new Thread(run);
        Thread T2 = new Thread(run);
        Thread T3 = new Thread(run);
        //三个窗口同时售票
        T2.start();
        T1.start();
        T3.start();
    }
}
main(多线程出票)

  4、原理

  同步方法的对象锁是谁?

  就是实现类对象new RunnableImpl();-----也就是this

  验证:

package day06.day06_3;

public class RunnableImpl implements Runnable {
    //定义票数量
    private int tickets = 100;
    @Override
    public void run() {
        //使用死循环 重复买票操作
        while (true){
            //调用同步方法
            method();
        }
    }
    //创建同步方法
    public /*synchronized*/ void method(){
       //验证同步方法的锁对象
        synchronized (this){
        if(tickets>0){
            //提高安全性 使用睡眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在 卖票
            System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票");
            tickets--;
        }}
    }
}
验证同步方法的锁对象
package day06.day06_3;

public class demo {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建三个窗口
        Thread T1 = new Thread(run);
        Thread T2 = new Thread(run);
        Thread T3 = new Thread(run);
        //三个窗口同时售票
        T2.start();
        T1.start();
        T3.start();
    }
}
多线程出票

Lock锁

  java.util.concurrent.locks.Lock 机制提供了synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

  public void lock();:加同步锁

  public void unlock();:释放同步锁

  java.util.concurrent.locks.Locks.RenntrantLock implements Losck接口

  1、使用步骤:

  1.在成员变量位置创建RenntrantLock实现类对象

  2.在可能出现线程安全问题的代码调用Lock接口中的方法lock()获取锁

  3.在可能出现线程安全问题的代码调用Lock接口中的方法unlock()释放锁

  2、例如

package day06.day06_4;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImpl implements Runnable {
    //定义票数量
    private int tickets = 100;
    //定义Lock ReentranLock对象
    Lock Suo = new ReentrantLock();
    @Override
    public void run() {
        //使用死循环 重复买票操作
        while (true){
            //在会出现线程安全的地方调用LOCK 的lock()
            Suo.lock();
            if(tickets>0){
                //提高安全性 使用睡眠
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在 卖票
                System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票");
                tickets--;
            }
            //在会出现线程安全的代码后调用unlock释放锁
            Suo.unlock();
        }
    }

}
Runnable实现类---Lock
package day06.day06_4;

public class demo {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建三个窗口
        Thread T1 = new Thread(run);
        Thread T2 = new Thread(run);
        Thread T3 = new Thread(run);
        //三个窗口同时售票
        T2.start();
        T1.start();
        T3.start();
    }
}
main()

  3、将unlock设置到finally中

  无论线程是否发生异常都会释放lock锁

package day06.day06_5;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImpl implements Runnable {
    //定义票数量
    private int tickets = 100;
    //定义Lock ReentranLock对象
    Lock Suo = new ReentrantLock();
    @Override
    public void run() {
        //使用死循环 重复买票操作
        while (true){
            //在会出现线程安全的地方调用LOCK 的lock()
            Suo.lock();
            if(tickets>0){
                //提高安全性 使用睡眠
                try {
                    Thread.sleep(1000);
                    //票存在 卖票
                    System.out.println(Thread.currentThread().getName()+"--->正在出售:"+tickets+"号票");
                    tickets--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //在会出现线程安全的代码后调用unlock释放锁----无论是否产生异常 都会释放锁
                    Suo.unlock();
                }
            }
        }
    }

}
Runnable实现类---Lock
package day06.day06_5;

public class demo {
    public static void main(String[] args) {
        //创建Runnable实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建三个窗口
        Thread T1 = new Thread(run);
        Thread T2 = new Thread(run);
        Thread T3 = new Thread(run);
        //三个窗口同时售票
        T2.start();
        T1.start();
        T3.start();
    }
}
main

 

 线程状态

概述:

  当线程被创建并启动后,它既不是一启动就进入执行状态也不是一直处于执行状态线程也是有生命周期的

线程的六种状态

   

 

   注意:

  我们无需研究这几种状态的实现原理,我们只需知道线程操作中存在这一的状态。

流程图

 

   注意:

  waitting表示的是无限等待状态,需要调用Object中的notify()方法去唤醒

 

posted @ 2021-03-25 19:55  心岛未晴  阅读(89)  评论(0)    收藏  举报