多线程基础篇

多线程基础篇

1.进程与线程

Java的第一大特色:多线程的编程支持

1.概念

进程:操作系统中一个程序的执行周期称为一个进程。
在DOS系统的时代,由于其本身就是一个单进程的操作系统,所以在同一时间段上只能够有一个程序执行。
后来发展到winodws系统后,我们发现多个程序可以同时执行,所以windows是一个多进程的操作系统。
线程:进程中的独立任务,OS中任务调度的最小单元,依赖进程存在。
与进程相比较,线程更"轻量级",创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

2.多进程与多线程区别

本质区别在于,每个进程拥有自己的一整套变量,而线程则共享数据。共享变量使得线程之间的通信比进程之间通信更有效、更方便

3.多线程的应用

在实际应用中,多线程非常有用。例如,一个浏览器应用可以同时下载多个图片、音乐;一个Web服务器需要同时处理多个并发的请求。这些都是多线程的应用。

4.高并发

高并发:访问的线程量非常非常高。
高并发带来的问题:服务器内存不够用,无法处理新的请求。

2.多线程的实现

1.通过实现继承Thread类来实现多线程

线程的启动是调用start方法
如果调用run方法,跟普通对象调用方法没有区别

  • 线程方法(start)只能调用一次,否则抛出IllegalThreadStateException异常

2.通过实现Runnable接口来实现多线程

看看Runnable的源码:

  • 可见这是一个函数式接口,内只含一个抽象方法,可以进行函数式编程
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

3.Callable接口实现多线程(有返回值)

注意:这是一个泛型接口,定义的泛型即为call方法的返回类型;

从JDK1.5开始追加了新的开发包:java.uti.concurrent。这个开发包主要是进行高并发编程使用的,包含很多在高并发操作中会使用的类。在这个包里定义有一个新的接口Callable;

使用方法:定义子类实现Callable接口,通过FutureTask和Thread(Runable target)构造实例化Thread对象

Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。

实例:

//泛型接口,在定义时需要指定类型,否则默认为Object;
class MyThreadC implements Callable<String> {
    private int ticket = 10;
    @Override
    public String call() throws Exception {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName()+"进行了售票,"+"票还剩余:"+ --ticket);
        }
        return "票已售完!!!";
    }
}
public class TestThread01 {
    public static void main(String[] args) {
        MyThreadC myThreadC = new MyThreadC();
        FutureTask<String> futureTask = new FutureTask<>(myThreadC);
        new Thread(futureTask).start();
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

输出:
Thread-0进行了售票,票还剩余:9
Thread-0进行了售票,票还剩余:8
Thread-0进行了售票,票还剩余:7
Thread-0进行了售票,票还剩余:6
Thread-0进行了售票,票还剩余:5
Thread-0进行了售票,票还剩余:4
Thread-0进行了售票,票还剩余:3
Thread-0进行了售票,票还剩余:2
Thread-0进行了售票,票还剩余:1
Thread-0进行了售票,票还剩余:0
票已售完!!!

这里大家可能会有疑问,不是同时两个开始售票的吗,为甚麽只有Thread-0在售票(默认命名方式),这里在后面就会讲到,在这里我们只需要关注Callable的用法;

4.利用线程池创建多线程(⭐优先考虑)

这里我们留到下一篇博客专门讲解多线程?

附加5. 继承Thread和实现Runnable的区别

  • 前者单继承,不能再继承其他类; 后者可实现多继承
  • 前者线程的创建调度跟业务耦合; 后者业务独立,可以复用(共享)

对于第二个,举例下面代码:

class MyThreadTick extends Thread {
    @Override
    public void run() {
        int tick = 10;
        while(tick > 0) {
            System.out.println("剩余票数:"+(tick--));
        }
    }
}

class MyRunnableTick implements Runnable {
    private int tick = 10;
    @Override
    public void run() {
        while(tick > 0) {
            System.out.println("剩余票数:"+tick--);
        }
    }
}
public class TestMyRunnableTick {
    public static void main(String[] args) {
        //下面这种方式没有对同一对象进行处理
//        new MyThreadTick().start();
//        new MyThreadTick().start();
//        new MyThreadTick().start();
        Runnable myRunnableTick = new MyRunnableTick();
        new Thread(myRunnableTick).start();
        new Thread(myRunnableTick).start();
        new Thread(myRunnableTick).start();
    }
}

输出:
剩余票数:10
剩余票数:8
剩余票数:7
剩余票数:6
剩余票数:5
剩余票数:4
剩余票数:3
剩余票数:2
剩余票数:1
剩余票数:9

附加6. Runnable的代理模式 ⭐

前面已经知道,Runnable是一个@FunctionalInterface声名的函数式接口,再来看看Thread的几种构造方法:

//第一种    
public Thread(Runnable target, String name) {
	init(null, target, name, 0);
}
//第二种
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

我们需要知道,要构建一个新的线程,始终要依靠Threa类来完成,所以单单是Runnable类是不行的;
所以我们只需要提供Runnable对象,就可以构造一个新的线程 ,这里其实Thread类就是一个代理类,而我们的Runnable是一个真实业务类,所以这里实现了简单的代理模式以后在遇到举例代理模式的例子时,这无疑不是一个很好的例子!!

3.多线程常用方法

1.获取当前进程名称

在Thread类中提供有这样一个构造方法:

    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

即我们在实例化Thread对象时可以直接为当前线程命名;

public class TestThread04 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println("Thread name :"+thread.currentThread().getName());
        new Thread(()-> {
            System.out.println("Thread name :"+thread.currentThread().getName());
        }
        ,"线程一").start();
        new Thread(()-> {
            System.out.println("Thread name :"+thread.currentThread().getName());
        }
        ,"线程二").start();
    }
}

线程的方法:

  • 通过Thread.currentThread()获取执行当前代码的线程

  • Java的main方法,程序入口,他也在线程中执行

  • 线程是Java应用程序执行的最小单元

  • 创建线程时,如果不指定线程名, 则默认为:Thread-index(0,1,2,3,4,5…)

    创建线程时通过构造方法,或者setName

public class TestThread04 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        //可见main函数也是一个线程
        System.out.println("Thread name :"+thread.currentThread().getName());
        new Thread(()-> {
            System.out.println("Thread name :"+thread.currentThread().getName());
        }
        ,"线程一").start();
        new Thread(()-> {
            System.out.println("Thread name :"+thread.currentThread().getName());
        }
        ,"线程二").start();
    }
}

输出:
Thread name :main
Thread name :线程一
Thread name :线程二

2.sleep方法(不会释放锁)

线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁(资源),也就是说如果当前线程持有对某个对象的锁(资源),则即使调用sleep方法,其他线程也无法访问这个对象

class MyRunnableTestSleep 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="+i);
        }
    }
}
public class TestThread05 {
    public static void main(String[] args) {
        MyRunnableTestSleep myRunnableTestSleep = new MyRunnableTestSleep();
        new Thread(myRunnableTestSleep).start();
        new Thread(myRunnableTestSleep).start();

    }
}

输出:
当前线程:Thread-1 i=0
当前线程:Thread-0 i=0
当前线程:Thread-0 i=1
当前线程:Thread-1 i=1
当前线程:Thread-1 i=2
当前线程:Thread-0 i=2
当前线程:Thread-1 i=3
当前线程:Thread-0 i=3
当前线程:Thread-0 i=4
当前线程:Thread-1 i=4
当前线程:Thread-1 i=5
当前线程:Thread-0 i=5

通过代码观察会错误的认为这三个线程是同时休眠的,但是千万要记住,所有的代码是依次进入到run()方法中的。真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行

3.join方法(会释放锁)

等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行
完毕之后在开始执行主线程。

例码:

class MyRunnableT implements Runnable {
    @Override
    public void run() {
        int i = 0;
        while(i<10) {
            System.out.print(i++ +" ");
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("\nfun方法结束!");
    }
}
public class TestThread01 {
    public static void main(String[] args) {
        MyRunnableT myRunnableT = new MyRunnableT();
        Thread thread = new Thread(myRunnableT);
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主方法结束!!");
    }
}

输出:
0 1 2 3 4 5 6 7 8 9
fun方法结束!
主方法结束!!

假如没有调用join方法,则将会输出:

主方法结束!!
0 1 2 3 4 5 6 7 8 9
fun方法结束!

4.yield()方法(线程让步)(不释放锁)

yield() : 让步,交出CPU时间不确定,由系统调度决定,只会让拥有相同优先级的线程有获取CPU的机会,不释放锁;

4.线程停止的3种方法(⭐)

线程的停止是多线程开发当中的一个非常重要的技术;

1. 设置标记位,可以使线程正常退出
class MyRunnableT implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while(flag) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread()+"第"+i+"次执行");
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class TestThread01 {
    public static void main(String[] args) {
        MyRunnableT myRunnableT = new MyRunnableT();
        Thread thread = new Thread(myRunnableT,"线程一");
        thread.start();
        try {
            Thread.sleep(4000);  //主线程休眠2000;不妨碍其他线程的执行,此时线程一仍然在执行;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myRunnableT.setFlag(false);
        System.out.println("主方法结束!");
    }
}

Thread[线程一,5,main]第1次执行
Thread[线程一,5,main]第2次执行
Thread[线程一,5,main]第3次执行
主方法结束!
Thread[线程一,5,main]第4次执行

2. 使用stop方法强制使线程退出(但是该方法不太安全所以已经被废弃了)
class MyRunnableT implements Runnable {

    @Override
    public void run() {
        int i = 1;
        while(true) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread()+"第"+i+"次执行");
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
public class TestThread01 {
    public static void main(String[] args) {
        MyRunnableT myRunnableT = new MyRunnableT();
        Thread thread = new Thread(myRunnableT,"线程一");
        thread.start();
        try {
            Thread.sleep(4000);  //主线程休眠2000;不妨碍其他线程的执行,此时线程一仍然在执行;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();   //在编译器中,这是过期代码(一条横线穿过)
        System.out.println("主方法结束!");
    }
}

Thread[线程一,5,main]第1次执行
Thread[线程一,5,main]第2次执行
Thread[线程一,5,main]第3次执行
主方法结束!

3.利用中断来终止
class MyRunnableT implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while(flag) {
            try {
                Thread.sleep(1000);
                boolean bool = Thread.currentThread().isInterrupted();
                if(bool) {
                    System.out.println("非阻塞情况下执行该操作  线程状态:"+bool);
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
                i++;
            } catch (InterruptedException e) {
                System.out.println("exit!!!");
                boolean bool = Thread.currentThread().isInterrupted();
                System.out.println(bool);
                return;
            }
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class TestThread01 {
    public static void main(String[] args) {
        MyRunnableT myRunnableT = new MyRunnableT();
        Thread thread = new Thread(myRunnableT,"线程A");
        thread.start();
        try {
            Thread.sleep(3000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main方法结束!!!");


    }
}

线程A第1次执行
线程A第2次执行
main方法结束!!!
exit!!!
false

上面的代码是通过异常退出的,这是因为run方法中有sleep方法的阻塞,从而从产生异常这一方式退出线程;

详细解析

interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
然而interrupte()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:

  • 如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个

InterruptedException;

  • 如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;例如,一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、jion、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false。

通过上面的分析,我们可以总结:
调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。 因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。

5.守护线程和用户线程(垃圾回收线程是守护,main不是)

守护线程是一种特殊的线程,它属于是一种陪伴线程。简单点说 java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法 来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

典型的守护线程就是垃圾回收线程。 只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
注意:主线程main是用户线程

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值