关于多线程(JAVA日志十五)


前言

开始正文前,先介绍两个概念,线程与进程。

进程

打开任务管理器,我们会看到任务管理器第一行下面就有两个字,进程。
在这里插入图片描述
在这里插入图片描述

那我们初步判断一下,进程是什么,是不是正在运行的程序,我们把左击选中,右击结束任务,就没有那个应用的进程了,应用也关闭了。显然,我们的猜测是正确的。

得出结论:进程是正在运行的程序

进程之间资源不共享。(试图想想每个应用的资源共享不就乱套了吗)


线程

线程:一个进程的执行单元
当我们启动java程序的时候至少启动了两个线程,一个是main方法主线程,一个是用于看护和回收垃圾的垃圾回收线程(GC)。

线程之间堆和方法区中内存共享。

并行

并行:指的是在同一时刻,多条指令在CPU上同时执行,多核的CPU有才能做到并行。

并发

并发:多条指令在CPU上轮流执行,谁抢到了执行权,谁就执行,大多数是单核的CPU,但是为什么我们感觉他们是同时执行的呢,比如用电脑边听音乐,边打游戏,没有那种切换了的感觉,因为我们人的辨别率较电脑而言很低,就像最初的电影一样,是用胶卷播放,而胶卷其实就是一张张照片,但在我们人看来,好像真的是图像在动。

多线程并发的好处:各线程独立进行,互不影响,提高效率。


提示:以下是本篇文章正文内容,下面案例可供参考

一、开启多线程

开启一个线程我们要用到Thread中的start()方法

//Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
//The result is that two threads are running concurrently: the current thread (which returns from the call to the start method) and the other thread (which executes its run method).
//It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.
//Throws:
//IllegalThreadStateException – if the thread was already started.
//导致该线程开始执行;Java虚拟机调用这个线程的run方法。
//结果是两个线程并发运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。
//多次启动一个线程是不合法的。特别是,线程在完成执行后可能不会重新启动。
//抛出:
//IllegalThreadStateException -如果线程已经启动。
public synchronized void start() {
.........
}

start()方法有什么用呢?首先,在主线程中,start方法压栈,开辟一个栈空间,随及弹栈,然后该线程开始执行,JVM自动调用run方法(run方法这需要我们去重写)。一句话,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。

注意

1.main方法执行完了,不代表程序执行完了,可能还有其他线程正在运行。

2.直接调用run()方法不是多线程!!!因为它没有开辟一个栈空间,实际上还是在主线程中运行的。

一、实现线程的两种方法

1.继承Thread类

public class Main {
    public static void main(String[] args){
        Test tt=new Test();
        tt.start();//启动线程
        for(int i=0;i<100;i++){
            System.out.println("主线程");
        }
    }
}
class Test extends Thread{
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println("Test线程");
        }
    }
}

在这里插入图片描述
我们多试几次,会发现,每次打印的内容都不一样,证明谁能枪到CPU的执行权的这个概率是随机的。

2.实现Runnable接口

在Thread的构造方法中有一个这样的构造方法,可以通过放Runnable的实现类的实例作为参数传入启动线程。

//Allocates a new Thread object. This constructor has the same effect as Thread (null, target, gname), where gname is a newly generated name.
//Automatically generated names are of the form "Thread-"+n, where n is an integer.
//Params:
//target – the object whose run method is invoked when this thread is started. 
//If null, this classes run method does nothing
//分配一个新的Thread对象。这个构造函数与Thread (null, target, gname)具有相同的效果,其中gname是新生成的名称。
//自动生成的名称格式为“Thread-”+n,其中n为整数。
//参数:
//目标-在线程启动时调用其run方法的对象。如果为空,这个类的运行方法什么也不做
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

例如:

public class Main {
    public static void main(String[] args){
        Test2 t2=new Test2();
        Thread tt=new Thread(t2);
        tt.start();
        for(int i=0;i<100;i++){
            System.out.println("主线程");
        }
    }
}
class Test2 implements Runnable{
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println("Test2线程");
        }
    }
}

我们也可以采用匿名内部类的形式:

public class Main {
    public static void main(String[] args){
        Thread tt=new Thread(new Runnable(){
            public void run(){
                for(int i=0;i<100;i++){
                    System.out.println("匿名内部类线程");
                }
            }
        });
        tt.start();
        for(int i=0;i<100;i++){
            System.out.println("主线程");
        }
    }
}

Runnable作为一个函数式接口,也可以用lambda表达式去做。

Thread tt=new Thread(()->{代码});

两种方法比较以下,哪种方法更好??
第二种,实现Runnable接口这种方法更好,也更推荐使用,为什么?因为java只能单继承,但可以做到多实现!!!


二、线程的生命周期

1.新建状态:指用new操作符创建一个线程,如new Thread(ran),这个线程还没有开始运行处于新建状态。

2.就绪状态:就绪状态的线程又叫做可运行状态,一旦调用start()方法就处于此状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态。

3.运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法接着上一次的代码继续住下执行。

4.阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,
此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片

5.结束状态:指当run方法自然退出或抛异常

start
JVM的调度
JVM的调度
遇到阻塞事件
阻塞解除
新建状态
就绪状态
运行状态
阻塞状态
结束状态

三、线程的常用方法

1.sleep

//        Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.
//                Params:
//        millis – the length of time to sleep in milliseconds
//        Throws:
//        IllegalArgumentException – if the value of millis is negative
//        InterruptedException – if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
//        使当前执行的线程在指定的毫秒数内休眠(暂时停止执行),这取决于系统计时器和调度器的精度和准确性。线程不会失去任何监视器的所有权。
//        参数:
//        以毫秒为单位的睡眠时间长度
//        抛出:
//        IllegalArgumentException -如果millis的值是负的
//        InterruptedException -如果任何线程中断了当前线程。抛出此异常时,清除当前线程的中断状态
   public static native void sleep(long millis) throws InterruptedException;

需要注意的是sleep()方法是是用static修饰:
举个例子:

public class Main {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            Thread.currentThread().setName("t线程");
            System.out.println(Thread.currentThread().getName()+"-->"+"正在执行");
        });
        try {
            t.start();
            t.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"-->"+"正在执行");
    }
}

请问输出结果是怎么样的??
在这里插入图片描述
休眠的线程是main线程,不是t线程,为什么???static!!!

2.join

//        Waits for this thread to die.
//                An invocation of this method behaves in exactly the same way as the invocation
//        join(0)
//        Throws:
//        InterruptedException – if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
//    等待线程死亡。
//    此方法的调用与调用的行为完全相同
//    join(0)
//    抛出:
//    InterruptedException -如果任何线程中断了当前线程。抛出此异常时,清除当前线程的中断状态。
	public final void join() throws InterruptedException {
        join(0);
    }

即等待指定的线程终止。
例如:

public class Main {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            Thread.currentThread().setName("t线程");
            for(int i=0;i<100;i++) {
                System.out.println(Thread.currentThread().getName() + "-->" +i+ "正在执行");
            }
        });
      try {
            t.start();
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i+"正在执行");
        }
    }
}

这个程序一定是t线程全部执行完了之后,主线程才会继续执行。

3.yield

//        A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
//        Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU. Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
//        It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.
//        给调度器的提示,表明当前线程愿意放弃当前对处理器的使用。调度器可以自由地忽略此提示。
//        Yield是一种启发式尝试,旨在改善线程之间的相对进度,否则会过度使用CPU。它的使用应该与详细的分析和基准测试相结合,以确保它确实具有预期的效果。
//        使用这种方法很少是合适的。它可能对调试或测试有用,因为它可能有助于重现由于竞争条件而导致的错误。在设计并发控制构造(如java.util.concurrent.locks包中的那些)时,它可能也很有用。
 public static native void yield();

同样要注意的是它是一个静态方法。
但又提一个问题,使用yield()方法后一定等另外一个线程执行完后才执行本线程吗??

public class Main {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            Thread.currentThread().setName("t线程");
            for(int i=0;i<100;i++) {
                System.out.println(Thread.currentThread().getName() + "-->" +i+ "正在执行");
                Thread.yield();
            }
        });
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i+"正在执行");
        }
    }
}

在这里插入图片描述
不一定!!!调用yield()方法只是表示t线程占到了CPU时间片但是它,不用了,进入到了就绪状态,但是是仍然有机会得到执行。

4.interrupt

//        Interrupts this thread.
//                Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.
//                If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.
//                If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a java.nio.channels.ClosedByInterruptException.
//        If this thread is blocked in a java.nio.channels.Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.
//                If none of the previous conditions hold then this thread's interrupt status will be set.
//        Interrupting a thread that is not alive need not have any effect.
//        Throws:
//        SecurityException – if the current thread cannot modify this thread
//        中断这个线程。
//        除非当前线程正在中断自己,否则将调用该线程的checkAccess方法,这可能导致抛出SecurityException。
//        如果这个线程在Object类的wait()、wait(long)或wait(long, int)方法的调用中被阻塞,或者在该类的join()、join(long)、join(long, int)、sleep(long)或sleep(long, int)方法的调用中被阻塞,那么它的中断状态将被清除,它将接收一个InterruptedException异常。
//        如果这个线程在InterruptibleChannel上的I/O操作中被阻塞,那么通道将被关闭,线程的中断状态将被设置,并且线程将接收一个java. io.channels. closedbyinterruptexception。
//        如果这个线程在java.nio.channels.Selector中被阻塞,那么线程的中断状态将被设置,它将立即从选择操作返回,可能带有非零值,就像调用了选择器的唤醒方法一样。
//        如果前面的条件都不成立,那么这个线程的中断状态将被设置。
//        中断非活动线程不需要产生任何影响。
//        抛出:
//        如果当前线程不能修改这个线程
public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

简而言之就是当该线程收到阻塞时,通过异常处理机制,抛出异常,从而结束当前的阻塞状态。

5.stop(弃子)

这个方法已经被放弃,其作用为直接终止一个线程。
为什么被抛弃了呢???试图想一下,该线程正在对数据进行操作,但你突然把这个线程关了,会造成什么,会造成数据损失,这不是我们所期望的。
我们可以怎么做去达到一样的效果呢,打标记。
例如:

public class Main {
    public static void main(String[] args) {
        Mythread mt=new Mythread();
        Thread t=new Thread(mt);
        t.start();
        for (int i = 0; i < 10; i++) {
            if(i==5){
                mt.flag=true;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i+"正在执行");
        }
    }
}

public class Mythread implements Runnable{
    boolean flag=false;
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            if(flag){
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i+"正在执行");
        }
    }
}

在这里插入图片描述
当执行了6秒时,t线程安全退出。

总结

以上就是今天要讲的内容,本文仅仅介绍了前言进程,线程,并行,并发
如何开启开启多线程以及注意事项;实现线程的两种方法,线程的生命周期,线程的常用方法1.sleep2.join3.yield4.interrupt5.stop(弃子)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值