Java多线程基本概念详解(上)

带着问题进行思考:

  • 线程的启动
  • 如何使线程暂停
  • 如何使线程停止
  • 线程的优先级
  • 线程安全相关的问题

一.程序,线程,进程的概念

程序:一种静态的概念,是指令与数据的集合
进程:它是动态的概念,它是程序的一次执行过程,资源分配和调度的基本单位。打开任务管理器,可以将运行中的.exe执行文件当做是进程。
线程:一个进程包括多个线程,进程中独立执行的子任务,一个线程只属于某个进程。

tips: 同类的多个线程共享堆和方法区的资源,但是每个线程都有自己的本地方法栈,程序计数器,虚拟机栈

注意: 多线程执行是异步的,切勿将代码中的顺序当做是执行的顺序,线程被调用时是随机的

tips: 同步与异步的区别?

同步:必须处理一件事后才能处理下一件事
异步:处理一件事的同时可以处理其他的事情,之间互不影响

二.线程创建的三种方式

①继承Thread类

public class A extends Thread{
    
    
    @Override
    public void run() {
    
    
        for(int i = 0; i < 20; i++){
    
    
            System.out.println("我在看书!!!!" + i);
        }
    }
    public static void main(String[] args) {
    
    
        A a = new A();
        a.start();
        for(int i = 0; i < 2000; i++){
    
    
            System.out.println("我在看代码!!!!" + i);
        }
    }
}

上述代码执行过程中,也就是说线程准备启动执行的时候,并不是立即执行的而是通过CPU调度进行交替的执行。启动线程的方法是使用start()方法,使线程处于就绪状态,而不是用run()方法去启动线程的,run()方法只是调用该线程所关联的执行代码run而已。

小案例:利用多线程实现网上图片的下载

导入common-io包

public class A extends Thread{
    
    
    private String url;//网络图片的地址
    private String name;//保存名字

    public A(String url, String name){
    
    
        this.url = url;
        this.name = name;
    }
//线程的执行体
    @Override
    public void run() {
    
    
        DownLoader downLoader = new DownLoader();
        downLoader.down(url, name);
        System.out.println("下载了文件"+ name);
    }
    
    public static void main(String[] args) {
    
    
        A a1 = new A("http://img.alicdn.com/imgextra/i1/131300861/O1CN01svWcZd1IENcQxIAaN_!!0-saturn_solar.jpg_220x220.jpg_.webp","1.jpg");
        A a2 = new A("http://img.alicdn.com/imgextra/i3/125593559/O1CN01uR4zA81cA4CmNXSmi_!!0-saturn_solar.jpg_220x220.jpg_.webp", "2.jpg");
        A a3 = new A("http://img.alicdn.com/imgextra/i2/18674716/O1CN01Bo0iJp1khyS4Yzfj7_!!0-saturn_solar.jpg_220x220.jpg_.webp", "3.jpg");
        a1.start();
        a2.start();
        a3.start();
    }
//下载图片
class DownLoader{
    
    
        public void down(String url, String name){
    
    
            try{
    
    
            FileUtils.copyURLToFile(new URL(url), new File(name));
            }catch (IOException e){
    
    
                e.printStackTrace();
                System.out.println("IO异常下载出错");
            }
        }
}
}

②实现Runable接口

public class B implements Runnable {
    
    

    @Override
    public void run() {
    
    
        for(int i= 0; i < 2000; i++){
    
    
            System.out.println("1111运行中==>" + i);
        }
    }
    public static void main(String[] args) {
    
    
        B b = new B();
        Thread thread = new Thread(b);
        thread.start();
        for(int i = 0; i < 3000; i++){
    
    
            System.out.println("2222运行中==>" + i);
        }
    }
}

③通过Callable和Future创建线程

/**
 * @author jektong
 * @Date 2020/6/15-10:17
 */
public class C implements Callable<Integer> {
    
    
    public static void main(String[] args) {
    
    
        C c = new C();
        FutureTask<Integer> ft = new FutureTask<>(c);
        Thread thread = new Thread(ft);
        thread.start();
        for(int i = 0; i < 1000; i++) {
    
    
            System.out.println("1111线程" + i);
        }
    }
    @Override
    public Integer call() throws Exception {
    
    
        int i = 0;
        for( i = 0; i < 1000; i++)
        {
    
    
            System.out.println("2222线程"+ i);
        }
        return i;
    }
}

补充:有人说线程创建的方式是四种,还有一种就是通过线程池的方式来创建线程。

案例:模拟龟兔赛跑的案列

/**
 * @author jektong
 * @Date 2020/6/15-13:29
 */
public class Race implements Runnable{
    
    
    //赢家
    private static String winner;

    @Override
    public void run() {
    
    
        for(int i = 0; i <= 100; i++){
    
    
            //让兔子睡觉
            if (Thread.currentThread().getName().equals("兔子") && i%10 == 0){
    
    
                try {
    
    
                    Thread.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            boolean flag = GameOver(i);
            if(flag){
    
    
                break;
            }
            System.out.println(Thread.currentThread().getName() 
                               + "-->跑了" + i + "步");
        }
    }
    //谁赢了比赛
    public Boolean GameOver(int steps){
    
    
        if(winner != null){
    
    
            return true;
        }else {
    
    
            if (steps >= 100){
    
    
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }
    public static void main(String[] args) {
    
    
        Race race = new Race();
        Thread thread1 = new Thread(race, "乌龟");
        Thread thread2 = new Thread(race, "兔子");
        thread1.start();
        thread2.start();
    }
}

三.线程停止

对于线程停止不可使用JDK提供的stop()destory()方法,推荐让线程自己停下来,主要采用以下的方式去停止线程:

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

通过一个外部的标志位去终止线程,代码如下:

/**
 * @author jektong
 * @Date 2020/6/15-15:49
 */
public class TestStop implements Runnable {
    
    
    //线程终止的标志位
    private boolean flag = true;
    @Override
    public void run() {
    
    
        int i = 0;
        while (flag){
    
    
            System.out.println("thread---run" + i);
        }
    }
    //设置公开的方法终止线程
    public void stop(){
    
    
        this.flag = false;
    }
    public static void main(String[] args) {
    
    
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for(int i = 0; i < 1000; i++){
    
    
            System.out.println("main===>" + i);
            if(i == 900){
    
    
                testStop.stop();
                System.out.println("此线程停止了");
            }
        }
    }
}

四.线程休眠

  • 使用sleep()方法休眠线程,括号可以指定时间(毫秒)
  • sleep存在InterruptedException异常
  • sleep时间到达后,线程呈就绪状态
  • sleep可以模拟网络延迟,倒计时等
  • 每个对象是一个锁,sleep不会释放锁

倒计时案例

/**
 * @author jektong
 * @Date 2020/6/15-16:41
 */
public class ThreadSleep{
    
    
    public static void down() throws InterruptedException{
    
    
        int num = 10;
        while (true){
    
    
            Thread.sleep(1000);
            System.out.println(num--);
            if(num <= 0){
    
    
                break;
            }
        }
    }
    public static void main(String[] args) {
    
    
        try {
    
    
            down();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

五.线程礼让

  • 使用yield()方法,让当前的线程暂停,但是不会阻塞
  • 将线程的状态变成就绪状态
  • CPU重新调度此线程,注意礼让不一定成功,要看CPU的状态
/**
 * @author jektong
 * @Date 2020/6/15-17:15
 */
public class ThreadYeild implements Runnable {
    
    
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName() + "线程开始");
        Thread.yield();//礼让线程,当然有可能礼让不成功
        System.out.println(Thread.currentThread().getName() + "线程结束");
    }
    public static void main(String[] args) {
    
    
        ThreadYeild threadYeild = new ThreadYeild();
        new Thread(threadYeild,"A").start();
        new Thread(threadYeild,"B").start();
    }
}

六.线程强制执行

使用join()的方法可以实现插队,此线程完成结束后,在执行其他的线程造成阻塞
一个插队小案例

/**
 * @author jektong
 * @Date 2020/6/15-18:20
 */
public class ThreadJoin implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 1000 ; i++) {
    
    
            System.out.println("VIP来了" + i);
        }
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
        thread.start();

        for (int i = 0; i < 100 ; i++) {
    
    
            //当主线程运行到i==50的时候阻塞
            if(i == 50){
    
    
                thread.join();//阻塞主线程
            }
            System.out.println("普通用户来了" + i);
        }
    }
}
  • 假设,有两个线程t1与t2,分别调用了start()方法,在t1后面调用了t1.join()方法
t1.start();
t1.join();
t2.start();
  • t1执行完毕后,t2执行
  • 如果是t1.join(1000),表示1s内执行t1如果未执行完毕,t2线程进入,与t1共享cpu资源

七.线程状态和线程的生命周期

  • NEW(至今未启动的线程)新建状态
  • RUNABLE(正在运行的状态) 运行状态
  • BLOCKED阻塞状态
  • WAITING等待状态
  • TIME_WATING超时状态
  • TERMINATED线程结束状态
/**
 * @author jektong
 * @Date 2020/6/15-20:33
 */
public class ThreadState implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5 ; i++) {
    
    
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
    
    
        ThreadState threadState = new ThreadState();
        Thread thread = new Thread(threadState);
        //观察状态NEW
        Thread.State state = thread.getState();
        System.out.println(state);
        //观察启动后RUN
        thread.start();
        state = thread.getState();
        System.out.println(state);
        //只要线程不终止,就一直跑下去
        while (state != Thread.State.TERMINATED){
    
    
            try {
    
    
                Thread.sleep(1000);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

生命周期:
在这里插入图片描述
线程中断或者结束,一旦是死亡状态后就不能再次启动

八.线程的优先级

  • 操作系统中将线程划分为各个优先级,优先级别越高,所分配的CPU资源也就越多
  • 线程优先级1-10的数字表示
  • Thread.MIN_PRIORITY = 1;
    Thread.NORM_PRIORITY = 5;
    Thread.MAX_PRIORITY = 10;

  • 使用getPriority()获取优先级,setPriority()设置优先级
  • 优先级高的不一定就是先执行的,只是执行的概率比较大

小测试

/**
 * @author jektong
 * @Date 2020/6/15-21:42
 */
public class ThreadPriority implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        ThreadPriority threadPriority = new ThreadPriority();
        Thread thread1 = new Thread(threadPriority);
        Thread thread2 = new Thread(threadPriority);
        Thread thread3 = new Thread(threadPriority);
        Thread thread4 = new Thread(threadPriority);
        Thread thread5 = new Thread(threadPriority);
        thread1.start();
        thread2.setPriority(1);
        thread2.start();
        thread3.setPriority(4);
        thread3.start();
        thread4.setPriority(9);
        thread4.start();
        thread5.setPriority(6);
        thread5.start();
    }
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()
                          + "--->" + Thread.currentThread().getPriority());
    }
}

输出结果显示
输出结果

九.守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须保证用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 比如后台记录的日志和GC的垃圾回收器都是属于守护线程的只是我们无法察觉到

小测试

/**
 * @author jektong
 * @Date 2020/6/15-22:20
 */
public class ThreadDem implements Runnable {
    
    

    @Override
    public void run() {
    
    
        int i = 0;
        while (true){
    
    
            try {
    
    
                i++;
                System.out.println("i===>" + i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
    
    
        ThreadDem threadDem = new ThreadDem();
        Thread thread = new Thread(threadDem);
        thread.setDaemon(true);//设置为守护线程
        thread.start();
        try {
    
    
            Thread.sleep(5000);
            System.out.println("运行完毕!thread对象不再打印了");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41857955/article/details/106749238