黑马程序员技术交流社区

标题: [学习交流] 【上海校区】多线程并发 了解 Thread [打印本页]

作者: 哈哈哈哈哈哈111    时间: 2019-12-31 10:18
标题: [学习交流] 【上海校区】多线程并发 了解 Thread
通过上一篇 多线程并发 (一) 了解 Java 虚拟机 - JVM 了解了java 虚拟机的构成以及对象的创建等。从Java虚拟机栈我们知道每当我们创建一个线程JVM就会给我们的线程分配一个私有的内存空间和程序计数器记录当前线程运行的次行代码的地址。了解了Thread之后,通过多线程编程进而引入锁(下篇)的概念。
1. 线程简介

线程是进程中可独立执行的最小单位,也是 CPU 资源分配的基本单位。

2. 线程的四个属性(1)编号Id
              线程的编号(id)用于标识不同的线程,每条线程拥有不同的编号。
    (2) 名字
             每个线程都有自己的名字(name),名字的默认值是 Thread-线程编号,比如 Thread-0 。除了默认值,我们也可以给线程设置名字,以我们自己的方式去区分每一条线程。
    (3)类别
            分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。GC线程是守护线程,对于守护线程JVM退出时不会考虑次线程是否持行完毕。
    (4)优先级
            线程的优先级(Priority)用于表示应用希望优先运行哪个线程,线程调度器会根据这个值来决定优先运行哪个线程。
            Java 中线程优先级的取值范围为 1~10,默认值是 5,Thread 中定义了下面三个优先级常量。
            最低优先级:MIN_PRIORITY = 1
            默认优先级:NORM_PRIORITY = 5
            最高优先级:MAX_PRIORITY = 10
3. 线程的六个方法

线程的常用方法有六个,它们分别是三个非静态方法 start()、run()、join() 和三个静态方法 currentThread()、yield()、sleep() 。我们只关注start()、join()、yield()、sleep(),另外run()、currentThread()容易理解。
(1)yield()
      顾名思义yield英文 让 的意思。当t.yield() 持行之后 t 回到就绪状态,所有线程重新抢用cpu。
(2)join()
      t2正在运行,这时在t2线程中调用 t1.join() ,这是让t2 等待线程t1 结束了,再运行。次方法可以让线程按顺序持行。自己线程中调用自己的join方法是无意义的。
(3)sleep()
      暂停阻塞等待一段时间,时间过了就继续。这个方法是不释放锁的。
(4)start()
     启动线程,让cpu调度器调度开启线程持行run()方法。而直接调用run()方法只是运行当前run方法,不会开启线程。

4.线程运行状态

线程调用状态图

分析:1. new 的状态(NEW)
           2. start() 之后线程到达就绪状态等待cpu调度,cpu调度了 就是运行状态。(RUNNABLE)
           3. 调用了 TImedWaiting 就回到就绪状态,结束之后自动到运行状态(TIMED_WAITING)
           4. 调用 Waiting 就绪然后持行相应的方法到运行(WAITING)
           5. 调用 Blocked 等待进入同步代码快的锁。(BLOCKED)
           6. 结束状态Teminated(TERMINATED)
线程被挂起:正在cpu中调度的线程,cpu把该线程丢出去,持行其他线程,这就是线程被挂起。之后又回来持续该线程。(cpu每次只能持行一个线程)
5.Java 线程调度原理

在任意时刻,CPU 只能执行一条机器指令,每个线程只有获取到 CPU 的使用权后,才可以执行指令。
也就是在任意时刻,只有一个线程占用 CPU,处于运行的状态。
多线程并发运行实际上是指多个线程轮流获取 CPU 使用权,分别执行各自的任务。
线程的调度由 JVM 负责,线程的调度是按照特定的机制为多个线程分配 CPU 的使用权。
线程调度模型分为两类:分时调度模型和抢占式调度模型。

1.分时调度模型
分时调度模型是让所有线程轮流获取 CPU 使用权,并且平均分配每个线程占用 CPU 的时间片。
2.抢占式调度模型
JVM 采用的是抢占式调度模型,也就是先让优先级高的线程占用 CPU,如果线程的优先级都一样,那就随机选择一个线程,并让该线程占用 CPU。
也就是如果我们同时启动多个线程,并不能保证它们能轮流获取到均等的时间片。
如果我们的程序想干预线程的调度过程,最简单的办法就是给每个线程设定一个优先级。
6.线程创建的四种方式
    1)继承Thread类创建线程
    2)实现Runnable接口创建线程
    3)使用Callable和Future创建线程
    4)使用线程池例如用Executor框架
具体代码也是非常简单,仅仅贴出Future的代码:
[AppleScript] 纯文本查看 复制代码
public class MyTask  implements Callable<Object>{    
    private String args1;
    private String args2;
    //构造函数,用来向task中传递任务的参数
    public  MyTask(String args1,String args2) {
        this.args1=args1;
        this.args2=args2;
    }
    //任务执行的动作
    @Override
    public Object call() throws Exception {
        
        for(int i=0;i<100;i++){
            System.out.println(args1+args2+i);
        }
        return true;
}

[AppleScript] 纯文本查看 复制代码
MyTask myTask = new MyTask("www", "www");
FutureTask<Object> futureTask = new FutureTask<>(myTask);
Thread thread = new Thread(futureTask);
thread.start();
//get()阻塞等待直到任务执行完成。
boolean result = (boolean) futureTask.get();


7.LockSupport
我们可以使用它来阻塞和唤醒线程,功能和wait,notify有些相似,但是LockSupport比起wait,notify功能更强大,也好用的多。
1)对比wait,notify
wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象。
当对象的等待队列中有多个线程时,notify只能随机选择一个线程唤醒,无法唤醒指定的线程。
LockSupport的话,我们可以在任何场合使线程阻塞,同时也可以指定要唤醒的线程。
[AppleScript] 纯文本查看 复制代码
public class LockSupportTest {

    public static void main(String[] args) {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();
        System.out.println("开始线程唤醒");
        LockSupport.unpark(parkThread);
        System.out.println("结束线程唤醒");

    }

    static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println("开始线程阻塞");
            LockSupport.park();
            System.out.println("结束线程阻塞");
        }
    }
}


可指定线程阻塞和唤醒。摘学于:https://www.cnblogs.com/takumicx/p/9328459.html
8.CountDownLatch
CountDownLatch开启一组线程,让每个线程去持行自己的任务,等所有线程都持行完毕后,再向下持行代码。比如我们开启一组线程去同步服务器的数据有 播放记录,下载记录,收藏记录等等用户信息。
[AppleScript] 纯文本查看 复制代码
CountDownLatch latch = new CountDownLatch(5);
        for (int i =0;i<5;i++){
            new Thread(new MyTask(latch,i*1000,i)).start();
        }
        try {
            latch.await();
            Log.e("WXY--","All the tasks are over.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

[AppleScript] 纯文本查看 复制代码
public class MyTask implements Runnable {
    CountDownLatch latch;
    int delayTime; // Test
    int curThread;
    public MyTask(CountDownLatch latch,int delayTime,int curThread) {
        this.latch = latch;
        this.delayTime = delayTime;
        this.curThread = curThread;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(delayTime);
            Log.e("WXY--","current task = " + curThread);
            latch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这篇相对简单主要是认识线程,了解线程的方法。介绍了两个线程同步的类。









欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2