多线程

参考资料 https://www.zhihu.com/question/65200684 https://www.zhihu.com/question/19901763

概念

进程和线程

  • 进程:进行中的程序,一片内存空间,本身不执行
  • 线程:进程中负责程序执行的控制单元,有多个线程就是多线程,一个进程中至少有一个线程
  • 开启多个线程是为了运行多段代码
  • CPU只同时执行一个进程中的一个线程,极快的速度切换,所以感觉程序在同时运行,比如文件拷贝速度会随着开的程序增加而变慢,可以增加CPU核心数

解析 JVM启动时启动多个线程,至少有两个线程

  • 执行main函数的主线程
  • 负责堆内存垃圾回收线程 垃圾回收器中调用Object中的finalize方法
//Demo
public class Demo extends Object {
    public void finalize(){
        System.out.println("垃圾回收");
    }
}
//main
new Demo();
new Demo();
System.gc();
new Demo();
Thread.sleep(100);
System.out.println("hello world");
//垃圾回收
//垃圾回收
//hello world

创建线程

继承Thread类

  1. 继承Thread类
  2. 覆盖Thread类的run方法
  3. 创建子类对象,
  4. 调用start方法启动线程,它会自动调用线程run方法,手动直接调用run是同步

注意

  1. 创建线程的目的是为了开启一条执行路径,去运行指定代码和其他代码同步运行,而运行的指定代码就是线程的任务;jvm创建的主线程在main函数中,而自定义的线程的任务代码在哪儿呢
  2. Thread类用于描述线程,线程需要任务,所以Thread类也对任务描述,这个任务就是通过run方法来体现,run方法就是封装自定义线程运行任务的函数
  3. 所以要继承Thread,并覆写run,实现我们的线程,将要运行的代码定义在run中
//Demo
public class Demo extends Thread {
    
    private String name;

    public Demo(String name) {
       super(name);
    }
    public void run(){
        show();
    }
    public void show(){
        for (int x=0;x<100;x++){
            //Thread的getName()可以获取线程名字 Thread-0开始
            //通过Thread.currentThread().getName()获取当前线程
            System.out.println(getName()+name+" :"+x);
        }

    }
}
//main
Demo d1 = new Demo("小强");
Demo d2 = new Demo("旺财");
d1.start();
d2.start();
System.out.println("hello world");

每个线程开辟不同的栈空间,异常也互不影响

实现Runnable

  1. 覆盖接口中的run,将线程任务代码封装在run中
  2. 通过Thread类创建线程对象,并将Runnable的子类对象作为Thread类的构造函数参数进行传递
  3. Thread对象start
public class Demo implements Runnable {

    private String name;

    public void run(){
        show();
    }
    public void show(){
        for (int x=0;x<100;x++){
            System.out.println(x+"..."+Thread.currentThread().getName());
        }

    }
}
//main
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();

实现Runnable的好处

  • 将线程的任务从线程的子类中分离出来,按照面向对象的思想将任务封装成对象
  • 避免java单继承的局限性

线程状态

卖票

继承Thread

public class Ticket extends Thread {

    private int num = 100;
    public void run(){
        while (true){
            if(num>0){
                System.out.println(num--);
            }
        }
    }
}
//main
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();

以上结果会打印400次 因为num是每个Ticket对象中的变量,不共享 将num定义为static也可以解决此问题,但不是多线程思想

Runnable

public class Ticket implements Runnable {

    private int num = 100;
    @Override
    public void run(){
        while (true){
            if(num>0){
                System.out.println(num--);
            }
        }
    }
}

//main
Ticket t = new Ticket();

Thread t1 = new Thread(t,"卖票窗口1");
Thread t2 = new Thread(t,"卖票窗口2");
Thread t3 = new Thread(t,"卖票窗口3");
Thread t4 = new Thread(t,"卖票窗口4");

t1.start();
t2.start();
t3.start();
t4.start();

暂停线程执行的方法

1.sleep : 不会释放锁,Sleep时别的线程也不可以访问锁定对象。 2.yield: 让出CPU的使用权,从运行态直接进入就绪态。让CPU 重新挑选哪一个线程进入运行状态。 3.join: 当某个线程等待另一个线程执行结束后,才继续执行 时,使调用该方法的线程在此之前执行完毕,也就是等待调 用该方法的线程执行完毕后再往下继续执行

TicketR tR =new TicketR();
Thread t1 = new Thread(tR,"卖票窗口1");
t1.start();

for(int i=0;i<10;i++) {
    if(i==5) {
        //t1.join();  //阻塞主线程,使t1执行结束后再执行主线程
        //Thread.sleep(2000); //使线程处于阻塞状态
        //Thread.yield(); //礼让一次,失去执行资格,处于就绪状态
        //Thread.stop() //停止线程,已过时
    }
    System.out.println(Thread.currentThread().getName()+"...."+i);
}

线程优先级

final int getPriority() 获取线程的优先级 final void setPriority(int priority) 设置线程的优先级


书籍推荐