玩命加载中 . . .

多线程(50)


多线程

概念

  1. 线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?

    • 每个进程是一个应用程序,都有独立的内存空间
    • 同一个进程中的线程共享其进程中的内存和资源
    • (共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己属于自己栈内存)
  2. 什么是进程?

    一个进程对应一个应用程序。例如:在 Windows操作系统启动Word就表示启动了一个进程。在java的开发环境下启动JVM,就表示启动了一个进程。现代的计算机都是支持多进程的,在同一个操作系统中,可以同时启动多个进程。

  3. 多进程有什么用?

    • 单进程计算机只能做一件事情。例:玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
    • 对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是。因为计算机的CPU只能在某个时间点上做一件事。计算机能以极快的速度来回切换线程,使得用户认为计算机运行两个线程。
  4. 什么是线程?

    • 线程是一个进程中的执行场景。一个进程可以启动多个线程。
      java多线程
  5. 多线程有什么作用?

    • 多线程不是为了提高执行速度,而是提高应用程序的使用率。
    • 线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程个栈。
      java线程生命周期
  6. 注:在此之前学过的都是单线程的程序!

  7. java线程的创建与启动:

    • 继承Thread类

      // 继承Thread类
      public class ThreadTest01 {
          public static void main(String[] args) {
              // 创建线程
              Thread t = new Processor(); // 多态父类型引用指向子类型对象
              // 启动线程
              t.start();
              for (int i = 0; i < 30; i++) {
                  System.out.println("main-->"+i);
              }
          }
      }
      class Processor extends Thread {
          // 重写run方法
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println("Processor-->"+i);
              }
          }
      
      }
      
    • 实现Runnable接口(推荐使用)

    
    // 实现Runnable接口(推荐使用)
    public class ThreadTest02 {
        public static void main(String[] args) {
            // 创建线程
            Thread t = new Thread(new Processor());
            // 启动线程
            t.start();
            for (int i = 0; i < 30; i++) {
                System.out.println("main-->"+i);
            }
        }
    }
    class Processor2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Processor2-->"+i);
            }
        }
    }
    

线程的调度与控制

通常我们的计算机只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。Java虚拟机要负责线程的调度,取得CPU的使用权目前有两种调度模型:分时调度模型和抢占式调度模型,Java使用抢占式调度模型。分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片抢占式调度模型:优先让优先级高的线程使用CPU(优先级高的线程获取的CPU时间片相对多一些),如果线程的优先级相同,那么会随机选择一个

  • 示例代码:

    // 创建线程第二种方式(推荐写法):
    public class ThreadTest02 {
        public static void main(String[] args) {
            // 创建线程
            Thread t = new Thread(new Processor());
            // 启动线程
            t.start();
            for (int i = 0; i < 30; i++) {
                System.out.println("main-->"+i);
            }
        }
    }
    class Processor2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Processor2-->"+i);
            }
        }
    }
    

线程的优先级

线程优先级主要分三种:MAX_PRIORITY(最高级);MIN_PRIORITY(最低级);NORM_PRIORITY(标准)默认。注:最高:10、标准:5、最低:1。

// 线程的优先级
public class ThreadTest04 {
    public static void main(String[] args) {
        // 优先级分为:1-10
        // 最小、标准、最大优先值
        System.out.println(Thread.MIN_PRIORITY);    // --> 1
        System.out.println(Thread.NORM_PRIORITY);   // --> 5
        System.out.println(Thread.MIN_PRIORITY);    // --> 10
        System.out.println("-------------------------");
        // 创建线程
        Thread t1 = new Thread(new Processor4());
        Thread t2 = new Thread(new Processor4());
        // 默认优先级都为5
        System.out.println("修改前"+t1.getPriority());   // --->5
        System.out.println("修改前"+t2.getPriority());   // --->6
        // 设置线程名称
        t1.setName("t1");
        t2.setName("t2");
        // 设置线程优先级
        t1.setPriority(5);
        t2.setPriority(6);
        System.out.println("修改后"+t1.getPriority());   // --->5
        System.out.println("修改后"+t2.getPriority());   // --->6
        // 启动线程
        t1.start();
        t2.start();
    }
}
class Processor4 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 80; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

线程堵塞

使用sleep()方法堵塞线程

// 1.Thread.sleep(毫秒)
// 2.sleep是一个静态方法
// 3.该方法的作用是:阻塞当前的线程腾出CPU,让给其他线程
public class ThreadTest05 {
    public static void main(String[] args) {

        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+"------>"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 创建Thread
        Thread t1 = new Thread(new Processor5());
        // 设置线程名称
        t1.setName("t1");
        // 启动线程
        t1.start();
    }
}
class Processor5 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"------>"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

yield()方法让给同一个优先级的线程让位。但是让位时间不固定。

注:yield()方法与sleep()方法相同,都是静态方法。

public class ThreadTest09 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Thread09());
        thread.setName("t");
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class Thread09 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            if (i % 20 == 0) {
                Thread.yield(); // 如果是20整数时候,让位给main线程
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

join()方法合并线程

注:1.此方法是个成员方法。
2.此方法在哪个线程,就跟哪个线程调用。

public class ThreadTest10 {
    public static void main(String[] args) {
        // 创建线程
        Thread t = new Thread(new Processor10());
        // 重命名线程名称
        t.setName("t");
        // 启动t线程
        t.start();
        // t线程与主线程合并
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class Processor10 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

中断堵塞

使用interrupt()方法异常中断堵塞

// 中断线程睡眠
// 让t线程中从堵塞状态中断:使用异常中断线程堵塞
public class ThreadTest07 {
    public static void main(String[] args) {
        Thread t = new Thread(new Processor7());
        // 设置线程名称
        t.setName("t");
        // 启动线程
        t.start();
        // 主线程延迟5秒后打断t线程的堵塞
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终端对t线程的堵塞
        t.interrupt();
    }
}
class Processor7 implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(3000000);
            System.out.println("HelloWorld!");  // 注:用异常强制中断睡眠,睡眠以下的语句不会执行!
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
     }
}

正常方法中断堵塞

public class ThreadTest08 {
    public static void main(String[] args) {
        Thread08 thread08 = new Thread08();
        Thread thread = new Thread(thread08);
        thread.setName("t"); // 设置线程
        thread.start(); // 启动线程
        try {
            Thread.sleep(5000);     // 启动线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread08.run = false; // 开关
        /*
        * 以上程序执行过程:
        * 创建线程并命名为t线程
        * 主线程睡眠5秒钟,在5S中内CPU时间片均为t线程的,但是t线程每次执行需要1S,所以到最后
        * 一秒钟时候,主线程结束堵塞,t线程被主线程关闭.*/
    }
}
class Thread08 implements Runnable {
    boolean run = true; // 声名t线程开关,默认此线程是开启的.
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (run) {        // t
                try {
                    Thread.sleep(1000); // 每次执行延迟1S
                    System.out.println("HelloWorld!!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->"+i);
            }else {
                return; // 如果t线程关闭则直接return
            }
        }
    }
}

线程同步

引入:

异步编程模型

同步编程模型

例:t1和t2线程:

异步编程模型:t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁。

同步编程模型:t1线程和七线程执行,当t线程必须等t线程执行结束之后,线程才能执行,这是同步编程模型。

什么时候要同步呢?为什么要引入线程同步呢?

1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制

线程同步机制使程序变成了(等同)单线程。

2.什么条件下要使用线程同步?

第一:必须是多线程环境

第二:多线程环境共享同一个数据

第三:共享的数据涉及到修改操作

以下程序演示取款例子。

多线程同时对同一个账户进行取款操作,会出现什么问题?

答:会出现余额数据没有及时更新而导致数据不安全.

对象锁

public class ThreadTest11 {
    public static void main(String[] args) {
        // 创建一个对象
        Account account = new Account("admin", 5000.0);
        //
        Thread t1 = new Thread(new Porcessor(account));
        t1.setName("t1");
        Thread t2 = new Thread(new Porcessor(account));
        t1.setName("t2");
        t1.start();
        // 延迟执行t2线程
/*        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        t2.start();
    }
}

// 取款流程
class Porcessor implements Runnable {
    // 创建对象
    Account account;

    // 构造函数
    public Porcessor(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.withdraw(1000.0);
        System.out.println("取款1000.0成功,余额:" + account.getBalance());
    }
}

// 抽象账户
class Account {
    // 账户名称
    private String actno;
    // 账户余额
    private double balance;

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    // 对外提供一个取款方法
    public void withdraw(double money) {

        // 把需要同步的代码,放到同步语句块中
        // 写法一
        synchronized (this) {       // 使用synchronized关键字将当前对象(this)使用对象锁
            double after = balance - money;

            // 延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 更新余额
            this.setBalance(after);
        }

    }
    // 写法二
    // 把需要同步的代码,放到取款方法中
/*    public synchronized void withdraw(double money) {       // 使用synchronized关键字将当前对象(this)使用对象锁
        double after = balance - money;

        // 延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新余额
        this.setBalance(after);
    }*/

    /*以上两种方法哪种更好?
    * 第一种方法更好;因为相比第二种方法第一种方法对需要同步的数据更加精准,极大的缩减的运行时间*/
}

类锁

当synchronized修饰静态方法时候,线程执行到此方法的时候会找类锁。

/* 类锁
* 以下程序m2方法会等m1方法执行完毕在执行m2方法吗?
* 答:不会因为以下程序的synchronized修饰的是静态方法
* 而修饰静态方法当线程执行到此处会直接使用类锁,与类锁无关
* 所以m2不会等待m1方法,但是由于主线程含有睡眠所以m1会优先执行*/
public class ThreadTest13 {
    public static void main(String[] args) {
        Myclass2 myclass2 = new Myclass2();
        Thread t1 = new Thread(new Processor13(myclass2));
        Thread t2 = new Thread(new Processor13(myclass2));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();

    }
}
class Processor13 implements Runnable {
    Myclass2 myClass2;

    public Processor13(Myclass2 myClass2) {
        this.myClass2 = myClass2;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")) {
            myClass2.m1();
        }else if (Thread.currentThread().getName().equals("t2")) {
            myClass2.m2();
        }
    }
}
class Myclass2 {
    public static synchronized void m1() {
        // 当synchronized修饰静态方法时候,线程执行到此方法的时候会找类锁.
        System.out.println("m1");
    }
    public static void  m2() {
        System.out.println("m2");
    }
}

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

// 死锁
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(new Myclass5(o1, o2));
        Thread t2 = new Thread(new Myclass6(o1, o2));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class Myclass5 implements Runnable {
    Object o1;
    Object o2;

    public Myclass5(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {

            }
        }
    }
}

class Myclass6 implements Runnable {
    Object o1;
    Object o2;
    public Myclass6(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {

            }
        }
    }
}

守护线程

/* 守护线程
* 当其他所有的用户线程结束,则守护线程退出
* 守护线程一般都是无限循环的
* 例:GC(垃圾回收)就是线程守护*/
public class ThreadTest15 {
    public static void main(String[] args) {
        Thread t = new Thread(new Processor15());
        t.setName("t");
        t.setDaemon(true);  // 将用户线程设置为守护线程
        t.start();  // 启动线程
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "------>" + i);
        }
    }
}
class Processor15 implements  Runnable {
    int i = 0;
    @Override
    public void run() {
        while (true) {
            i++;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "------>" + i);
        }
    }
}

计定时器

// 定时器
public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建定时器
        try {
        Timer t = null;
            t = new Timer();
            t.schedule(new Processor16(),new SimpleDateFormat("yyy-MM-dd HH:mm:ss SSSS").parse("2021-10-8 23:48:00 0000"),1000*60);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Processor16 extends TimerTask {
    @Override
    public void run() {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSSS").format(new Date()));
    }
}

文章作者: 小靳同学
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小靳同学 !
评论
 上一篇
反射(51) 反射(51)
反射 概述:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和
2021-10-10
下一篇 
IO流(49) IO流(49)
IO流字符流 字符输入流 FileReader 字节输出流 FileWriter 字节流 字节输入流 FileInputStream 字节
2021-10-03
  目录