线程基础、线程之间的共享和协作

基础概念

1.CPU核心数、线程数

中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据

cpu的核心数与线程数是1:1的关系,例如一个8核的cpu,支持8个线程同时运行。但在intel引入超线程技术以后,cpu与线程数的关系就变成了1:2。

我们平时在开发的时候,感觉并没有受cpu核心数的限制,想启动线程就启动线程,哪怕是在单核CPU上,为什么?这是因为操作系统提供了一种CPU时间片轮转机制。 时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XstVAdJx-1583834587048)(D:\youruike\并发编程\线程基础、线程之间的共享和协作\assets\1575362840970.png)]

2.CPU时间片轮转机制

cpu给每个进程分配一个“时间段”,这个时间段就叫做这个进程的“时间片”,这个时间片就是这个进程允许运行的时间,如果当这个进程的时间片段结束,操作系统就会把分配给这个进程的cpu剥夺,分配给另外一个进程。如果进程在时间片还没结束的情况下阻塞了,或者说进程跑完了,cpu就会进行切换。cpu在两个进程之间的切换称为“上下文切换”,上下文切换是需要时间的,大约需要花费5000~20000(5毫秒到20毫秒,这个花费的时间是由操作系统决定)个时钟周期,尽管我们平时感觉不到。所以在开发过程中要注意上下文切换(两个进程之间的切换)对我们程序性能的影响

3.什么是进程和线程

进程:它是属于程序调度/运行的资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘IO等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。

线程:它是cpu调度的最小单位,必须依赖于进程而存在,线程本身是不能独立进行的,它必须依附某个进程,线程本身是不拥有系统资源的。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

线程无处不在,任何一个程序都必须要创建线程,特别是Java不管任何程序都必须启动一个main函数的主线程; Java Web开发里面的定时任务、定时器、JSP和 Servlet、异步消息处理机制,远程访问接口RM等,任何一个监听事件, onclick的触发事件等都离不开线程和并发的知识。

4.什么是并行和并发

**并行:**例如一个饭堂有八个窗口,也就是说,同一时刻可以有8个人进行打饭,那么也就说这个饭堂的并行度是8
**并发:**它严格说起来是要与一个时间单位相关的,简单来说就是一个时间段内可以同时处理的事情数。例如这个饭堂有8个窗口,每个人打饭需要花费30秒钟,那么一分钟内(8个窗口 * 60秒 / 每人打饭需要30秒 = 16)这个饭堂的并发度就是16
总结:并行是同一时刻可以处理多少件事,并发是在单位时间内可以处理多少件事情

5.高并发编程的意义、好处和注意事项

通过以上的了解,我们可以知道高并发编程可以充分利用cpu的资源,例如一个8核的cpu跑一个单线的程序,那么意味着在任意时刻,我有7个CPU核心是浪费掉的。另外可以充分地加快用户的响应时间。同时使用并发编程可以使我们的代码模块化、异步化
注意事项/难点: 有与线程之间会共享进程的资源,既然说是共享资源,就有可能存在冲突。在高并发编程中如果控制不好,还有可能会造成线程的死锁(什么是死锁在我之后的文章会详细讲解)。每启动一个线程,操作系统就需要为这个线程分配一定的资源,线程数太多还可能会把内容消耗完毕,会导致系统死机

认识Java里的线程

1. Java程序天生就是多线程的

一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。

为什么说Java是多线程得呢?我们来一段代码分析下

package com.yrkedu;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class GetThread {
    public static void main(String[] args) {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        /**
         * 锁的细节显示
         * 而这两个参数分别控制两种锁ThreadInfo .getLockedMonitors()  和 ThreadInfo.getLockedSynchronizers()
         * a.  Monitor 锁
         * 就是我们传统使用的synchronized(Object obj),
         * 可以通过MonitorInfo[]得到具体的锁的数量和信息
         * b. Locked ownable synchronizers 锁
         * 常指的ReentrantLock 和 ReentrantReadWriteLock 锁
         * 通过得到LockInfo[]  可以得到具体的类,锁的数量和信息
         */
        ThreadInfo[] threadInfos = bean.dumpAllThreads(false, false);
        for (ThreadInfo info : threadInfos) {
            System.out.println("线程ID:"+info.getThreadId()+"---->线程名称:"+info.getThreadName());
        }
    }
}

打印结果:

线程ID:6---->线程名称:Monitor Ctrl-Break
线程ID:5---->线程名称:Attach Listener
线程ID:4---->线程名称:Signal Dispatcher
线程ID:3---->线程名称:Finalizer
线程ID:2---->线程名称:Reference Handler
线程ID:1---->线程名称:main

[6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给JVM信号的线程
[3] Finalizer // 调用对象finalize方法的线程
[2] Reference Handler//清除Reference(引用)的线程
[1] main //main线程,用户程序入口

2.启动线程的方法

① 类Thread
② 接口Runnable(推荐使用这种,因为接口可以多实现)
③ 接口Callable:与Runnable的区别是,实现Runnabble接口里的run方法是没有返回值的,而Callable是允许有返回值的

  • 继承Thread类

    package com.yrkedu.mythread;
    
    public class MyThread extends Thread{
    
        @Override
        public void run() {
            //super.run();
            System.out.println("这是通过继承Thread类实现的线程");
        }
    }
    
  • 实现Runnable接口

    package com.yrkedu.mythread;
    
    public class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("这是通过实现Runnable接口实现的线程");
        }
    }
    
  • 实现Callable接口

    package com.yrkedu.mythread;
    
    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("这是通过实现Callable实现的线程");
            return "hello Callable!!";
        }
    }
    
  • TestThread.java测试类

    package com.yrkedu.mythread;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class TestThread {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //继承Thread
            new MyThread().start();
    
            //实现Runnable接口
            Runnable runnable = new MyRunnable();
            new Thread(runnable).start();
    
            //实现Callable接口
            Callable<String> callable = new MyCallable();
            //FutureTask继承了RunnableFuture继承了Runnable
            FutureTask<String> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
            System.out.println("MyCallable的返回内容--->"+futureTask.get());
    
        }
    }
    

3.终止线程的方法

① 线程自然终止(run方法执行完毕)
② 抛出了一个未处理的异常导致线程提前结束

调用stop方法

暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

Interrup()中断

安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,一般的阻塞方法,如sleep等本身就支持中断的检查,检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

中断继承Thread类的线程

package com.yrkedu.endthread;

public class EndThread extends Thread{

    public EndThread(String name){
        super(name);
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        /*分别设置三种条件进行测试*/
        /*while (true){
            System.out.println(threadName+" is run!");
        }*/
        while (!isInterrupted()){
            System.out.println(threadName+" is run!");
        }
        /*while (!Thread.interrupted()){
            System.out.println(threadName+" is run!");
        }*/
        System.out.println(threadName+"的中断标示为"+isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new EndThread("endThread");
        thread.start();
        Thread.sleep(20);
        thread.interrupt();
    }
}

中断实现Runnable的线程

package com.yrkedu.endthread;

public class EndRunnable implements Runnable{

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        /*isInterrupted是Thread的方法,不能直接使用*/
        /*while (!isInterrupted()){
            System.out.println(threadName+" is run!");
        }*/
       /* while (true){
            System.out.println(threadName+" is run!");
        }*/
        while (!Thread.currentThread().isInterrupted()){
            System.out.println(threadName+" is run!");
        }
        System.out.println(threadName+"的中断标示为"+Thread.currentThread().isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new EndRunnable(),"endThread");
        thread.start();
        Thread.sleep(20);
        thread.interrupt();
    }
}

中断阻塞状态的线程

package com.yrkedu.endthread;

public class HasInterruptedExceptionThread extends Thread{

    public HasInterruptedExceptionThread(String name){
        super(name);
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        while (!isInterrupted()){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                System.out.println(threadName+"的中断标示为:"+isInterrupted());
                //要想中断抛出了InterruptedException的线程,必须使用interrupt();
                //interrupt();
                e.printStackTrace();
            }
            System.out.println(threadName+"的中断标示为-->"+isInterrupted());
        }
        System.out.println(threadName+"的中断标示为"+isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new HasInterruptedExceptionThread("exceptionThread");
        thread.start();
        Thread.sleep(50);
        thread.interrupt();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AX24cYj1-1583834587050)(D:\youruike\并发编程\线程基础、线程之间的共享和协作\assets\timg.jpg)]

Java线程调度的一点背景

在各种各样的线程中,Java虚拟机必须实现一个有优先权的、基于优先级的调度程序。这意味着Java程序中的每一个线程被分配到一定的优先权,使用定义好的范围内的一个正整数表示。优先级可以被开发者改变。即使线程已经运行了一定时间,Java虚拟机也不会改变其优先级

优先级的值很重要,因为Java虚拟机和下层的操作系统之间的约定是操作系统必须选择有最高优先权的Java线程运行。所以我们说Java实现了一个基于优先权的调度程序。该调度程序使用一种有优先权的方式实现,这意味着当一个有更高优先权的线程到来时,无论低优先级的线程是否在运行,都会中断(抢占)它。这个约定对于操作系统来说并不总是这样,这意味着操作系统有时可能会选择运行一个更低优先级的线程。

理解线程的优先权

接下来,理解线程优先级是多线程学习很重要的一步,尤其是了解yield()函数的工作过程。

  1. 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
  2. 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. t.setPriority()用来设定线程的优先级。
  7. 记住在线程开始方法被调用之前,线程的优先级应该被设定。
  8. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级

setPriority()优先级设置

1 . 优先级表示重要程度或者紧急程度.但是能不能抢到资源也是不一定.
2 . 分配优先级:反映线程的重要或紧急程度
线程的优先级用1~10 表示,1的优先级最低,10的优先级最高,默认值是5

/**
 * 优先级   : 只能反映 线程 的 中或者是 紧急程度 , 不能决定 是否一定先执行
 * setPriority()
 * 1~10   1最低  10最高    5是默认值
 */
public class Test {

    public static void main(String[] args) {
        MyThread thread = new MyThread("ange");
        thread.setPriority(1);
        MyThread thread2 = new MyThread("rock");
        thread2.setPriority(10);
        MyThread thread3 = new MyThread("pika");
        MyThread thread4 = new MyThread("tiger");
        thread4.setPriority(3);
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

class MyThread extends Thread{

     public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

关于多线程中sleep、join、yield、wait的区别

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.
     *  意思是说:当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 该线程不会失去任何监视器的所有权。
     * @param  millis
     *         the length of time to sleep in milliseconds  
     *         毫秒为单位
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
public static native void sleep(long millis) throws InterruptedException;

其实主要的就是他是让其他线程走,自己进行休眠,但是自己却不会释放对象锁,也就是说,如果有同步锁的时候,其他线程不能访问共享数据。

注意该方法要捕获异常 比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。 总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。

2、yield() 方法

class SubTread extends Thread{
	@Override
	public void run() {
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}	
}
public class TestThread {
	public static void main(String[] args) {
		SubTread st1 = new SubTread();
		st1.setName("子线程1");
		st1.start();
		Thread.currentThread().setName("==========主线程");
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
			if(i % 10 ==0){
				Thread.currentThread().yield();
			}
		}
	}
}

结果:

==========主线程:1
子线程1:1
==========主线程:2
子线程1:2
==========主线程:3
子线程1:3
子线程1:4
子线程1:5
子线程1:6
子线程1:7
子线程1:8
子线程1:9
子线程1:10
子线程1:11
==========主线程:4
子线程1:12
子线程1:13
子线程1:14
==========主线程:5
子线程1:15
==========主线程:6
子线程1:16
==========主线程:7
子线程1:17
==========主线程:8
子线程1:18
子线程1:19
==========主线程:9
子线程1:20
==========主线程:10
子线程1:21
子线程1:22
==========主线程:11
子线程1:23
==========主线程:12
子线程1:24
==========主线程:13
子线程1:25
子线程1:26
==========主线程:14
子线程1:27
==========主线程:15
子线程1:28
==========主线程:16
子线程1:29
==========主线程:17
子线程1:30
==========主线程:18
子线程1:31
==========主线程:19
子线程1:32
==========主线程:20
子线程1:33
==========主线程:21
子线程1:34
==========主线程:22
子线程1:35
==========主线程:23
子线程1:36
==========主线程:24
子线程1:37
==========主线程:25
子线程1:38
子线程1:39
子线程1:40
==========主线程:26
==========主线程:27
==========主线程:28
==========主线程:29
==========主线程:30
==========主线程:31
==========主线程:32
==========主线程:33
子线程1:41
子线程1:42
子线程1:43
子线程1:44
子线程1:45
子线程1:46
子线程1:47
子线程1:48
子线程1:49
子线程1:50
==========主线程:34
子线程1:51
==========主线程:35
子线程1:52
==========主线程:36
子线程1:53
==========主线程:37
子线程1:54
==========主线程:38
子线程1:55
子线程1:56
子线程1:57
子线程1:58
子线程1:59
子线程1:60
子线程1:61
子线程1:62
子线程1:63
子线程1:64
子线程1:65
==========主线程:39
子线程1:66
==========主线程:40
子线程1:67
==========主线程:41
子线程1:68
==========主线程:42
子线程1:69
==========主线程:43
子线程1:70
==========主线程:44
==========主线程:45
==========主线程:46
子线程1:71
==========主线程:47
子线程1:72
==========主线程:48
子线程1:73
==========主线程:49
子线程1:74
子线程1:75
子线程1:76
==========主线程:50
子线程1:77
==========主线程:51
子线程1:78
==========主线程:52
子线程1:79
==========主线程:53
子线程1:80
==========主线程:54
子线程1:81
==========主线程:55
子线程1:82
==========主线程:56
子线程1:83
==========主线程:57
子线程1:84
==========主线程:58
子线程1:85
==========主线程:59
==========主线程:60
子线程1:86
==========主线程:61
子线程1:87
==========主线程:62
子线程1:88
子线程1:89
子线程1:90
子线程1:91
子线程1:92
子线程1:93
子线程1:94
子线程1:95
子线程1:96
==========主线程:63
子线程1:97
==========主线程:64
子线程1:98
==========主线程:65
子线程1:99
==========主线程:66
子线程1:100
==========主线程:67
==========主线程:68
==========主线程:69
==========主线程:70
==========主线程:71
==========主线程:72
==========主线程:73
==========主线程:74
==========主线程:75
==========主线程:76
==========主线程:77
==========主线程:78
==========主线程:79
==========主线程:80
==========主线程:81
==========主线程:82
==========主线程:83
==========主线程:84
==========主线程:85
==========主线程:86
==========主线程:87
==========主线程:88
==========主线程:89
==========主线程:90
==========主线程:91
==========主线程:92
==========主线程:93
==========主线程:94
==========主线程:95
==========主线程:96
==========主线程:97
==========主线程:98
==========主线程:99
==========主线程:100

子线程打印从1-100的数字,主线程也打印1-100的数据

在主线程执行过程中当打印的数字为整数时调用yield()方法,让出cpu的执行权,这时主线程和子线程str1都可以争夺cpu的执行权,即我们在结果中可以看到有主线程打印整数后,子线程打印的情况;也有主线程打印整数后,主线程继续打印的情况。

/**
     * 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.
     *   意思是说 提示当前线程可以让处理器忽略当前线程,去处理其他线程
     * <p> 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.
     * 它是一种启发式尝试,用于改善线程之间的相对进展,否则会过度利用CPU。 它的使用应与详细的分析和基准测试相结合,以确保它实际上具有所需的效果。
     * <p> 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
     * {@link java.util.concurrent.locks} package.
     *  使用这种方法很少是合适的。 它可能对调试或测试目的很有用,它可能有助于重现因竞争条件而产生的错误。 在设计并发控制结构(如中的那些)时,它也可能很有用
     */
public static native void yield();

yield() 这个方法从以上注释可以看出,也是一个休眠自身线程的方法,同样不会释放自身锁的标识,区别在于它是没有参数的,即yield()方 法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。

3、join() 方法

这个方法比较有意思,Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前, B不能工作。

class SubTread extends Thread{
 
	//2.重写Thread里的run方法,方法内实现此子线程要完成的功能
	@Override
	public void run() {
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
 
public class TestThread1 {
	public static void main(String[] args) {
		SubTread st1 = new SubTread();
		st1.setName("子线程1");
		st1.start();
		Thread.currentThread().setName("==========主线程");
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
			if(i == 20){
				try {
					st1.join();
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
	}
}

结果:

==========主线程:1
==========主线程:2
==========主线程:3
子线程1:1
子线程1:2
子线程1:3
==========主线程:4
子线程1:4
==========主线程:5
子线程1:5
子线程1:6
==========主线程:6
子线程1:7
==========主线程:7
子线程1:8
==========主线程:8
==========主线程:9
==========主线程:10
子线程1:9
==========主线程:11
子线程1:10
子线程1:11
子线程1:12
子线程1:13
子线程1:14
子线程1:15
子线程1:16
子线程1:17
子线程1:18
子线程1:19
子线程1:20
子线程1:21
子线程1:22
子线程1:23
子线程1:24
子线程1:25
子线程1:26
子线程1:27
子线程1:28
子线程1:29
子线程1:30
子线程1:31
子线程1:32
子线程1:33
子线程1:34
子线程1:35
==========主线程:12
==========主线程:13
==========主线程:14
==========主线程:15
==========主线程:16
==========主线程:17
==========主线程:18
==========主线程:19
==========主线程:20
子线程1:36
子线程1:37
子线程1:38
子线程1:39
子线程1:40
子线程1:41
子线程1:42
子线程1:43
子线程1:44
子线程1:45
子线程1:46
子线程1:47
子线程1:48
子线程1:49
子线程1:50
子线程1:51
子线程1:52
子线程1:53
子线程1:54
子线程1:55
子线程1:56
子线程1:57
子线程1:58
子线程1:59
子线程1:60
子线程1:61
子线程1:62
子线程1:63
子线程1:64
子线程1:65
子线程1:66
子线程1:67
子线程1:68
子线程1:69
子线程1:70
子线程1:71
子线程1:72
子线程1:73
子线程1:74
子线程1:75
子线程1:76
子线程1:77
子线程1:78
子线程1:79
子线程1:80
子线程1:81
子线程1:82
子线程1:83
子线程1:84
子线程1:85
子线程1:86
子线程1:87
子线程1:88
子线程1:89
子线程1:90
子线程1:91
子线程1:92
子线程1:93
子线程1:94
子线程1:95
子线程1:96
子线程1:97
子线程1:98
子线程1:99
子线程1:100
==========主线程:21
==========主线程:22
==========主线程:23
==========主线程:24
==========主线程:25
==========主线程:26
==========主线程:27
==========主线程:28
==========主线程:29
==========主线程:30
==========主线程:31
==========主线程:32
==========主线程:33
==========主线程:34
==========主线程:35
==========主线程:36
==========主线程:37
==========主线程:38
==========主线程:39
==========主线程:40
==========主线程:41
==========主线程:42
==========主线程:43
==========主线程:44
==========主线程:45
==========主线程:46
==========主线程:47
==========主线程:48
==========主线程:49
==========主线程:50
==========主线程:51
==========主线程:52
==========主线程:53
==========主线程:54
==========主线程:55
==========主线程:56
==========主线程:57
==========主线程:58
==========主线程:59
==========主线程:60
==========主线程:61
==========主线程:62
==========主线程:63
==========主线程:64
==========主线程:65
==========主线程:66
==========主线程:67
==========主线程:68
==========主线程:69
==========主线程:70
==========主线程:71
==========主线程:72
==========主线程:73
==========主线程:74
==========主线程:75
==========主线程:76
==========主线程:77
==========主线程:78
==========主线程:79
==========主线程:80
==========主线程:81
==========主线程:82
==========主线程:83
==========主线程:84
==========主线程:85
==========主线程:86
==========主线程:87
==========主线程:88
==========主线程:89
==========主线程:90
==========主线程:91
==========主线程:92
==========主线程:93
==========主线程:94
==========主线程:95
==========主线程:96
==========主线程:97
==========主线程:98
==========主线程:99
==========主线程:100

在代码中设置当主线程打印20时调用子线程的join()方法,这时主线程会停止执行,等待子线程执行结束,主线程继续执行。

/**
     * Waits for this thread to die.  
     *  等待线程死亡
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
public final void join() throws InterruptedException {
    join(0);  // 调用了有参方法
}

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *  这个线程最多等多少毫秒,如果超时了,就会进行线程死锁
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
public final synchronized void join(long millis)
    throws InterruptedException {

保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

4、wait方法

wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用,也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。

注意,它们都是 Object 类的方法,而不是 Thread 类的方法。

wait() 方法与 sleep() 方法的不同之处在于,wait() 方法会释放对象的“锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。

除了使用 notify() 和 notifyAll() 方法,还可以使用带毫秒参数的 wait(long timeout) 方法,效果是在延迟 timeout 毫秒后,被暂停的线程将被恢复到锁标志等待池。

此外,wait(),notify() 及 notifyAll() 只能在 synchronized 语句中使用,但是如果使用的是 ReenTrantLock 实现同步,该如何达到这三个方法的效果呢?解决方法是使用 ReenTrantLock.newCondition() 获取一个 Condition 类对象,然后 Condition 的 await(),signal() 以及 signalAll() 分别对应上面的三个方法。

守护线程setDaemon

/**
* setDaemon
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为 true,则将该线程标记为守护线程。
*/

public class ThreadDemo3 {
    public static void main(String [] args){
        TestThread1 tt = new TestThread1();
        Thread t = new Thread(tt);
        t.setDaemon(true);
        t.start();
        t.interrupt();
    }
}

class TestThread1 implements Runnable{
    public void run()
    {
        try{
            while (!Thread.currentThread().isInterrupted())
            {
                System.out.println(Thread.currentThread().getName()+"is running");
            }
        }finally{
            //这里面的内容不一定会执行的
        }
    }

}
//输出结果为空,因为所有线程都为守护线程,Java 虚拟机退出

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
应用背景为变电站电力巡检,基于YOLO v4算法模型对常见电力巡检目标进行检测,并充分利用Ascend310提供的DVPP等硬件支持能力来完成流媒体的传输、处理等任务,并对系统性能做出一定的优化。.zip深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值