多线程(上)

270 阅读5分钟

这是小卡斯蒂的第一篇学习博客。其实这些天来完全忘记了要在这发学习博客的任务呢。 虽然多线程早学完了,但是忘了好多,所以写篇博客用来巩固一下。 浅用下markdown(不是很会用)

多线程

一共有四种方式创建线程:jdk5.0后又添加了2种、

重点: 1.线程的创建与使用

2.线程的同步

3.jdk5.0新增线程创建方式

程序,进程,线程

程序:一段静态代码

进程:正在运行的一个程序。有自身的产生,存在和消亡的过程-称之为生命周期。是动态

线程:进程可进一步细化为线程,是程序内部的一条执行路径

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

线程作为调度和执行的单位,每个线程都有独立的运行栈(图1.1中的虚拟机栈)和程序计数器(图1.1有所体现),方法区和堆则是一份进程一份(图1.1也有所体现),线程转换的开销小

一个进程可能包含着多个线程,那么这些线程就会共享相同的内存空间

内存结构:图1.1

image.png 多线程的缺点:可能会带来安全隐患,也就是线程不安全

这时就需要线程同步来解决

单核CPU和多核CPU

image.png

并行与并发

并行:多个CPU同时执行多个任务。例:多个人做不同的事,每个人拿着自己的球搁那投篮

并发:一个CPU同时执行多个任务,例如:多个人做同一件事,打3v3篮球

使用多线程的优点

image.png

什么时候用到多线程:

1.程序需要同时执行2或多个任务

2.程序需要实现一些需要等待的任务,如用户输入,文件读写操作等

3.需要一些后台运行的程序时

线程的创建和使用:

PS:下图不是多线程

image.png

方式1:继承于Thread类

1.创建一个Thread的子类

2.重写Thread类中的run()方法-->将此线程执行的操作声明在run中

3.创建Thread类的子类的对象

4.通过此对象调用start()(1.启动当前线程2.调用当前线程的run方法(单独的调用run方法不叫创建线程))

例子:遍历100以内所有的偶数

image.png 具体流程是这样的:先执行主线程main方法,主线程中创建对象,再调用start方法启动副线程。若start下还有代码,则还是主线程进行执行,那么此时就会产生交互,主线程副线程一同执行,这就称之为多线程。

问题:

1.一个对象只能调用一次start

2.要想再启动一个线程,只能通过再new一个对象的方式并调用start方法

练习:创建两个分线程,其中一个遍历100以内的偶数,另一个遍历100以内的奇数

思路:由于两要求不同,因此需要创建2个子类

image.png 也可以这样子:通过匿名子类并重写run方法实现

image.png

Thread类的有关方法:

image.png

image.png 其他方法: currentThread:静态方法,返回执行当前代码的线程(此方法很重要 )

yield:静态方法,使当前正在执行的线程向另一个线程交出运行权。

join:在线程a中调用线程b的join方法,此时线程a进入阻塞状态,直到线程b完成执行以后,线程a才结束阻塞状态。

stop:停止该线程。这个方法以及弃用

sleep(long millitime):让当前线程睡眠指定的millitime毫秒。在指定的millitime毫秒内,当前线程是阻塞状态

isAlive:判断当前线程是否还存活

线程的调度

image.png

image.png setPriority:在start方法前就修改

说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着,只有当高线程的线程执行完后,低优先级的线程才执行

习题

创建3个窗口卖100张票

public class sold {//创建3个窗口卖100张票

    public static void main(String[] args) {
        window t1 = new window();
        window t2 = new window();
        window t3 = new window();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class window extends Thread{
    private static int ticket = 100;//设置为static,ticket才能被3个线程共用
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                System.out.println(getName() + ":卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

但是使用该方法时ticket需要加上static关键字才能共享数据,因此引出创建多线程的方式2

创建多线程的方式2:实现Runnable接口

1.创建一个实现了Runnable接口的类

2.实现类去实现Runnable中的抽象方法:run

3.创建实现类的对象

4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5.通过Thread类的对象调用start方法

下图为具体实现

public class threadtest2 {
    public static void main(String[] args) {
        Mthread mthread = new Mthread();
        Thread t1 = new Thread(mthread);//利用构造器
        t1.start();
    }
}
class Mthread implements Runnable{//实现Runnable接口
    @Override
    public void run() {//重写run方法
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(i);
            }

        }
    }
}

此时线程为t1而不是mthread

源码分析一下为何此时Thread调用的是Mthread中重写的run:

在Thread的源码中,构造器Thread(Runnable target)中的target若是重写了run方法,那么就调用target的run方法

image.png

在上上图中体现为:Thread调用的run方法其实是构造器中的参数Mthread中重写的run方法。

若是还想创建一个线程:

直接:Thread t2 = new Thread(Mthread);t2.start();即可

活学活用之使用上述方式完成卖票例题:

public class solds {
    public static void main(String[] args) {


        Mthread m = new Mthread();
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        Thread t3 = new Thread(m);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Mthread implements Runnable{
    private int ticket = 100;//这里不用加static的原因是三个线程共用这个Mthread

    @Override
    public void run() {
        while (true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName() + "卖票,票号为" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

两种创建方式的总结

实现接口的方式好一些

原因:1.第一种方式中,由于有些类可能要继承其他类,而java只支持单继承,因此会出现不得不使用接口的方式

2.上述例子中,说明实现的方式天然就能共享数据,而继承的方式只能由static才能共享数据

两者的联系:Thread本身也实现了Runnable接口

相同点:两种方式都要重写run方法,将线程要执行的逻辑声明在run方法中