Java--多线程

Posted MinggeQingchun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java--多线程相关的知识,希望对你有一定的参考价值。

以下对并发、并行、进程、线程的理解引自 阿里巴巴技术专家 Hollis

Java并发编程

一、并发

并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。

无论是Windows、Linux还是MacOS等其实都是多用户多任务分时操作系统。使用这些操作系统的用户是可以“同时”干多件事的

二、并行

并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式称之为并行(Parallel)

三、进程

对于操作系统来说,一个任务,一个应用程序就是一个进程(Process)

如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就是启动一个记事本进程

四、线程

在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)

如在一个记事本上打字,画图等子任务,这些子任务之间共用同一个进程资源

进程当做资源分配的基本单元,线程当做执行的基本单元,同一个进程的多个线程之间共享资源

1、单核CPU如何做到多线程并发

实际上,对于单个CPU的计算机来说,在CPU中,同一时间只能干一件事儿。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。

如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。

由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。

所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。

2、线程创建的三种方式

1、继承java.lang.Thread类,重写run方法

/**
 1、实现线程有3种方式:
    1、编写一个类,直接继承java.lang.Thread,重写run方法
    2、编写一个类实现java.lang.Runnable接口
    3、编写类实现Callable接口,通过FutureTask创建
 2、启动线程
 调用线程对象的start()方法
 start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间
 run()方法不会启动线程,不会分配新的分支线程栈
 */
public class ThreadNewByExtend 
    public static void main(String[] args) 
        // 这里是main方法,这属于主线程,在主栈中运行

        // 新建一个分支线程对象
        Mythread t = new Mythread();
        /**
         * run()方法不会启动线程,不会分配新的分支栈(只调用run()不调用start()还是单线程)
         * 会先执行 t 线程的 for循环,再执行 主线程main中的for循环
         * */
        //t.run();

        /**
         * start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
         * start()方法只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
         * 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
         * run方法在分支栈的栈底部,main方法在主栈的栈底部;分支线程run和 主线程main是平级的
         * */
        t.start();

        //主线程for循环
        for (int i = 1;i <= 100;i++)
            System.out.println("main----" + i);
        
    


class Mythread extends Thread 
    //Ctrl+O
    @Override
    public void run() 
        for (int i = 1;i <= 100;i++)
            System.out.println("Mythread----" + i);
        
    

2、编写类实现java.lang.Runnable接口

/**
 编写一个类实现java.lang.Runnable接口
 */
public class ThreadNewByRunnable 
    public static void main(String[] args) 
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

        //采用匿名内部类方式
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i = 1;i <= 100;i++)
                    System.out.println("分支线程t1----" + i);
                
            
        );
        t1.start();

        //主线程for循环
        for (int i = 1;i <= 100;i++)
            System.out.println("main----" + i);
        
    


class MyRunnable implements Runnable

    @Override
    public void run() 
        for (int i = 1;i <= 100;i++)
            System.out.println("分支线程t----" + i);
        
    

3、编写类实现Callable接口,通过FutureTask创建(JDK8新特性)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
实现线程的第三种方式:
    实现Callable接口
    优点:可以获取到线程的执行结果
    缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
 */
public class ThreadNewByCallable 
    public static void main(String[] args) 

        // 创建一个“未来任务类”对象,参数:Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() 
            @Override
            // call()方法就相当于run方法,这个方法有返回值
            public Object call() throws Exception 
                System.out.println("call begin");
                Thread.sleep(1000 * 5);
                System.out.println("call end");

                return 100+50;
            
        );

        Thread t = new Thread(task);
        t.setName("t");
        t.start();

        try 
            // get()方法获取t线程的返回结果,但是会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
         catch (InterruptedException e) 
            e.printStackTrace();
         catch (ExecutionException e) 
            e.printStackTrace();
        

        // main方法这里的程序要想执行必须等待get()方法的结束,而get()方法可能需要很久
        // get()方法是为了拿另一个线程的执行结果
        System.out.println("Hello World!");
    

3、线程常用方法

/**
1、获取当前线程对象
    Thread t = Thread.currentThread();
2、获取线程对象的名字
    String name = 线程对象.getName();
3、修改线程对象的名字
    线程对象.setName("线程名字");
4、当线程没有设置名字的时候,默认的名字
    Thread-0
    Thread-1
    .....
 */
public class ThreadMethod 
    public static void main(String[] args) 
        //获取当前线程对象,currentThread()
        Thread t = Thread.currentThread();

        //获取线程名字"main",getName()
        System.out.println(t.getName());

        Thread t1 = new Thread();
        Thread t2 = new Thread();
        //线程默认名字:Thread-0;Thread-1
        System.out.println(t1.getName());//Thread-0
        System.out.println(t2.getName());//Thread-1

        //设置线程名字,setName()
        t1.setName("t1");
        System.out.println(t1.getName());//t1
    

4、线程调度,线程优先级

(1)线程调度模型

        【1】抢占式调度模型:
            哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些
            java采用的就是抢占式调度模型

        【2】均分式调度模型:
            平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样

(2)线程调度方法

【1】实例方法:
            void setPriority(int newPriority) 设置线程的优先级
            int getPriority() 获取线程优先级
            最低优先级1
            默认优先级是5
            最高优先级10
            优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
【2】静态方法:
            static void yield()  让位方法
            暂停当前正在执行的线程对象,并执行其他线程
            yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用
            yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”
            注意:在回到就绪之后,有可能还会再次抢到。
【3】实例方法:
            void join()  

/**
 * 线程优先级
 *
 * void setPriority(int newPriority)    设置线程的优先级
 * int getPriority()                    获取线程优先级
 *
 * 最低优先级1;默认优先级是5;最高优先级10
 *
 * */
public class ThreadPriority 
    public static void main(String[] args) 
        System.out.println("线程最高优先级:"+Thread.MAX_PRIORITY);//10
        System.out.println("线程最低优先级:"+Thread.MIN_PRIORITY);//1
        System.out.println("线程默认优先级:"+Thread.NORM_PRIORITY);//5

        //线程默认优先级
        System.out.println(Thread.currentThread().getName() + "线程的默认优先级是:" + Thread.currentThread().getPriority());

        Thread t = new Thread(new Runnable() 
            @Override
            public void run() 
                for(int i = 0; i < 10000; i++)
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                
            
        );
        t.setName("t");
        t.setPriority(10);
        t.start();

        Thread.currentThread().setPriority(1);

        // 优先级较高的,只是抢到的CPU时间片相对多一些。
        // 大概率方向更偏向于优先级比较高的。
        for(int i = 0; i < 10000; i++)
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        
    

5、 线程状态图

线程是有状态的,并且这些状态之间也是可以互相流转的

Java中线程的状态分为6种:

1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法

2、运行(RUNNABLE):Java线程中将就绪(READY)和运行中(RUNNING)两种状态笼统的称为“运行”。

(1)就绪(READY):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配cpu使用权 。

(2)运行中(RUNNING):就绪(READY)的线程获得了cpu 时间片,开始执行程序代码

3、阻塞(BLOCKED):表示线程阻塞于锁(关于锁,在后面章节会介绍)

4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)

5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回

6、终止(TERMINATED):表示该线程已经执行完毕。

以上是关于Java--多线程的主要内容,如果未能解决你的问题,请参考以下文章

java 多线程

java线程池

java单例双重检查锁为啥需要加volatile关键字

多线程

多线程详解

JAVA-初步认识-第十二章-多线程概述