Java 并发——多线程基础

摘要:
Thead类与Runnable接口Java的线程,即一个Thread实例。Java的线程执行过程有两种实现方式:子类继承Thread类,并且重写voidrun()方法。继承Thread类方式的多线程:编程简单。voidstart():使该线程开始执行;Java虚拟机调用该线程的run方法。线程状态控制booleanisAlive()测试线程是否处于活动状态。voidjoin等待该线程终止的时间最长为millis毫秒+nanos纳秒。staticvoidyield()暂停当前正在执行的线程对象,并执行其他线程。ThreadGroupgetThreadGroup():返回该线程所属的线程组。线程的生命周期1新建和就绪状态当程序使用new关键字创建了一个线程
Thead类与Runnable接口

Java的线程,即一个Thread实例。

Java的线程执行过程有两种实现方式:

  • 子类继承Thread类,并且重写void run()方法。
  • 自定义类实现Runnable接口,并且实现void run()方法。并在Thead构造时,将Runnable实例放入Thead。

Thread类

创建一个新线程必须实例化一个Thread对象。

使用方法:

  1. 子类继承Thread类。重写Thread的run()方法。
  2. 实例化该子类。
  3. 执行Thread的start()方法启动一个线程。

例:Thread使用方法

/***  Thread类。
*/
class MyThread extendsThread {    
    public voidrun() {
    //...  线程执行
}
}
public classTestThread1 {
    public static voidmain(String args[]) {
        MyThread thread = new MyThread ();    //新建线程。
        thread .start();                    //线程开始执行。
        //主线程其他方法
}
}

Runnable接口

实现Runnable接口需要实现run()方法.run()方法代表线程需要完成的任务.因此把run方法称为线程执行体.

使用方法:

  1. 类实现Runnable接口,并且实现run()方法。
  2. 实例化该类。Runnable runnable=new MyRun();
  3. 把该类注入到Thread对象中,即通过Thread的构造方法注入。Thread thread=new Thread(runnable);
  4. 调用Thread实例的start()方法。启动线程。thread.start();

例:Runnable使用方法

class MyRunner implements Runnable {    //实现Runnable接口
    public voidrun() {
    //......    线程执行
}
}

public classTestThread1 {
    public static voidmain(String args[]) {
        MyRunner runner= newRunner1(); 
        Thread t = new Thread(runner);    //新建线程。
        t.start();                   //线程开始执行。
        //......    主线程继续执行
}

}

两种方式所创建线程的对比

实现Runnable接口方式的多线程:

  • 编程稍复杂。
  • 如果需要访问当前线程,必须使用Thread.currentThread()方法。
  • 线程只是实现了Runnable接口,还可以继承其他类。
  • 在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU,代码,数据分开,形成清晰的模型,较好地体现了面向对象的思想。

继承Thread类方式的多线程:

  • 编程简单。
  • 如果需要访问当前线程直接使用this。
  • 已经继承了Thread类,无法再继承其他父类。
Thread的常用API

1 Thread类的构造方法

  • Thread()
  • Thread(Runnable target)
  • Thread(Runnable target, String name)
  • Thread(String name)
  • Thread(ThreadGroup group, Runnable target)
  • Thread(ThreadGroup group, Runnable target, String name)
  • Thread(ThreadGroup group, Runnable target, String name, long stackSize)
  • Thread(ThreadGroup group, String name)

2常用方法

(1) 启动线程

  • void run() :如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
  • void start() :使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

(2) 线程状态控制

  • boolean isAlive() 测试线程是否处于活动状态。
  • int getPriority() 返回线程的优先级。
  • void setPriority(int newPriority) 更改线程的优先级。
  • static void sleep(long millis)
  • static void sleep(long millis, int nanos) 当前线程暂停运行指定的毫秒数(加指定的纳秒数),但此线程不失去已获得的锁旗标.
  • void join() 等待该线程终止。
  • void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。
  • void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
  • void interrupt() 中断线程。 调用该方法引发线程抛出InterruptedException异常。
  • static boolean interrupted() 测试当前线程是否已经中断。
  • boolean isInterrupted() 测试线程是否已经中断。
  • static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
  • Thread.State getState() 返回该线程的状态。

(3) 当前线程

  • static Thread currentThread() : 返回对当前正在执行的线程对象的引用。
  • String getName() : 返回该线程的名称.
  • void setName(String name) : 设置线程名称.
  • void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
  • boolean isDaemon() :测试该线程是否为守护线程。
  • String toString() : 返回该线程的字符串表示形式,包括线程名称 优先级和线程组。
  • long getId() :返回该线程的标识符。
  • void checkAccess() :判定当前运行的线程是否有权修改该线程。
  • ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
  • static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
  • Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用 的处理程序。

(4) 线程组

  • static int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
  • StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
  • static int activeCount() :返回当前线程的线程组中活动线程的数目。
  • ThreadGroup getThreadGroup() :返回该线程所属的线程组。

(5) 其他

  • static Map<Thread,StackTraceElement[]> getAllStackTraces() 返回所有活动线程的堆栈跟踪的一个映射。
线程的生命周期

Java 并发——多线程基础第1张

1 新建和就绪状态

当程序使用new关键字创建了一个线程后,该线程就处于新建状态。JVM为Thread对象分配内存。初始化其成员变量的值。线程对象调用start()方法之后,该线程处于就绪状态。 JVM会为其创建方法调用栈和程序计数器。
就绪状态的线程并没有开始运行,它只是表示该线程可以运行了。JVM的线程调度器调度该线程运行。

注意:

  • 调用start()启动线程之后,run()方法才会被看作线程执行体。
  • 直接调用run()方法,则run()方法就只是一个普通方法。

2 运行和阻塞状态

就绪状态的线程获得了CPU,开始执行run方法的线程执行体。则该线程处于运行状态。线程在执行过程中可能会被中断,以使其他线程获得执行的机会,线程调度取决于底层平台采用的策略。

现代桌面和服务器操作系统一般都采用抢占式策略。一些小型设备如手机则可能采用协作式调度。抢占式策略的系统:系统给每个可执行的线程一个小时间段来处理任务;当该时间段用完,系统会剥夺该线程所占有的资源,让其他线程获得执行机会.在选择下一个线程时,系统会考虑线程的优先级.

3 线程进入阻塞状态

  1. 线程调用sleep方法主动放弃所占用的处理器资源。
  2. 线程调用了一个阻塞式的IO方法,该方法返回之前,该线程被阻塞。
  3. 线程试图获得一个同步监视器,但同步监视器正被其他线程所持有。
  4. 线程在等待某个通知(notify)。
  5. 线程调用了线程的suspend方法将线程挂起。不过这个方法容易导致死锁,所以程序应该尽量避免使用该方法。

4 阻塞线程重写进入就绪状态

  1. 调用sleep方法的线程经过了指定的时间。
  2. 线程调用的阻塞式IO方法已经返回。
  3. 线程成功地获得了试图取得同步监视器。
  4. 线程正在等待某个通知时,其他线程发出了一个通知。
  5. 处于关闭状态的线程被调用了resume恢复方法。

5 线程死亡

线程在以下情况下死亡:

  • run()方法执行完成,线程正常结束.
  • 线程抛出一个未捕获的Exception或Error.
  • 直接调用该线程的stop()方法来结束该线程.该方法容易导致死锁,通常不推荐使用.

Thread对象的isAlive()方法,查看线程是否死亡。

注意:

  • 死亡的线程不能再用start()方法重新启动。
  • 一个线程的start()方法不能两次调用。

6 线程睡眠:sleep

sleep方法将线程转入阻塞状态。时间到即再转入就绪状态。

线程的操作与特性

1 线程让步: yeild

yeild()方法是静态方法。该方法使当前线程让出CPU资源,继续参与线程竞争。

2 join线程

public final void join() throwsInterruptedException
public final synchronized void join(long millis) throws InterruptedException

join()方法表示当前线程等待指定线程结束,等待的线程结束后继续执行当前线程

例:join方法示例。

public classJoinMain {
    public volatile static int i=0; 
    public static class AddThread extendsThread{ 
        @Override 
        public voidrun() {
            for(i=0;i<10000000;i++); 
        }
    }

    public static void main(String[] args) throwsInterruptedException { 
        AddThread at=newAddThread(); 
        at.start(); 
        at.join();     //当前线程(主线程)等待at线程运行结束。
System.out.println(i); 
    } 
}

join的本质

查看join方法的源码,可以了解到,join即相当于以下代码:

while( 指定的线程.isAlive()) {
    wait(0);
}

当前线程会一直调用wait()方法,所以当前线程会一直处于等待状态。当被等待的线程结束(即指定的线程结束),JVM会自动调用notifyAll()方法来通知当前线程wait已经结束。注意,此处不要手动调用thread.notifyAll()方法。(关于join方法,JDK文档有更详细的说明)

3 线程优先级

每个线程都有优先级。优先级高的线程获得较多的执行机会。默认情况下,main线程具有普通优先级。每个线程默认优先级与创建它的父线程具有同样的优先级。

Java提供的优先级范围是1~10。默认优先级为5。

Thread提供静态常量:

  • static int MAX_PRIORITY 线程可以具有的最高优先级。 值为10。
  • static int MIN_PRIORITY 线程可以具有的最低优先级。值为1。
  • static int NORM_PRIORITY 分配给线程的默认优先级。值为5。

注意:

  • 不同操作系统的优先级不同.应尽量避免直接为线程指定优先级,而应使用以上三个静态常量类设置优先级.

4 后台线程

运行在后台,用于为用户线程提供服务。又称为“守护线程”。所有用户线程都结束后,后台线程也会结束,JVM退出。

  • main方法的主线程是前台线程。
  • 前台线程创建的线程默认是前台线程。后台线程创建的线程默认是后台线程。

比较:

  • 普通线程:若主线程结束,普通线程不会结束,JVM不会退出。
  • 守护线程:若主线程以及所有普通线程都结束,则后台线程会直接死亡,JVM退出。

常用API

  • 调用Thread对象的setDeamon(true)方法可以将指定线程设置成后台线程。
  • Thread对象的isDeamon()方法用于判断指定线程是否为后台线程。

5 停止线程

若需要停止线程,不推荐使用stop()方法。stop方法强制线程停止,切线程会释放所有monitor。由于stop方法过于粗暴,已经被废弃。

例:现在有两条记录。两条线程的其中一个线程要写对象,另一个线程要读对象。两个线程对对象加锁。

Java 并发——多线程基础第2张

若使用stop方法,可能导致数据一致性的错误。

public classStopThreadTest {
    
    static Student stu = newStudent();
    
    public static void main(String[] args) throwsInterruptedException {
        
        Thread writeThread = newThread(){
            @Override
            public voidrun() {
                //假设writeThread比readThread先拿到stu的锁。
                synchronized(stu){
                    stu.id = 2;    //可能该语句执行完后,主线程中 writeThread.stop(); 开始产生作用。
                    //若主线程执行 writeThread.stop();则该线程在此处将强行结束。
java_label_stop:
                    stu.name = "小王";
                }
            }
        };
        
        Thread readThread = newThread(){
            @Override
            public voidrun() {
                //假设writeThread比readThread先拿到stu的锁。
                synchronized(stu){
                    //若在java_label_stop处writeThread被结束,则该语句将打印出 2 ; 小明 。从而导致数据一致性错误
                    System.out.println(stu.id + ";" +stu.name);
                }
                
            }
        };
        
        
        //主线程设置stu。
        stu.id = 1;
        stu.name = "小明";
        
        //假设writeThread比readThread先拿到stu的锁。
writeThread.start();
        readThread.start();
        
        //强制停止写线程。
writeThread.stop();
    }
    
}

classStudent{
    public intid;
    publicString name;
}

6 线程中断

public void Thread.interrupt()               //中断线程。修改线程的中断状态,但线程本身不会有任何响应,依旧运行。

public boolean Thread.isInterrupted()        //判断是否被中断。

public static boolean Thread.interrupted()   //判断是否被中断,并清除当前中断状态

调用线程中断方法只是给线程“打个招呼”,线程不会本身不会做任何相应。

例:调用interrupt()方法,线程依旧运行。

public classInterruptThreadTest {
    public static voidmain(String[] args) {
        
        Thread t1 = newThread(){
            
            @Override
                public voidrun(){ 
                    while(true){ 
                        Thread.yield(); 
                    } 
                }
        };

        t1.start();        //启动线程。
        t1.interrupt();    //终端线程,线程会依旧运行。
}
}

线程中断方法可以用于结束线程,非常优雅方便。

例:线程外部使用中断方法,结束线程。

public classInterruptStopThreadTest {
    public static voidmain(String[] args) {
        Thread t1 = newThread() {

            @Override
            public voidrun() {
                while (true) {
                    //检测线程被中断
                    if(Thread.currentThread().isInterrupted()) {
                        //若线程中断,则推出run方法。
                        System.out.println("Interruted!");
                        break;
                    }

                    Thread.yield();
                }
            }
        };

        t1.start();     //启动线程。
        t1.interrupt(); //终端线程,但线程依旧运行。t1.isInterrupted()方法将返回true。
}
}

InterruptedException异常

大部分线程的等待方法都会抛出InterruptedException异常,中断标志位将会被清空。Java方法中默认的等待线程一旦被interrupt(),则等待方法会立即抛出InterruptedException异常。

例:处于sleep的线程被interrupt,立即抛出InterruptedException。

public classInterruptedExceptionTest {
    public static void main(String[] args) throwsInterruptedException {
        Thread t1 = newThread() {
            
            @Override
            public voidrun() {
                while (true) {
                    if(Thread.currentThread().isInterrupted()) {
                        System.out.println("Interruted!");
                        break;
                    }
                    try{
                        Thread.sleep(2000);
                    } catch(InterruptedException e) {
                        System.out.println("Interruted When Sleep");
                        //抛出异常后会清除中断标记位。所以重新设置中断状态。
Thread.currentThread().interrupt();
                    }
                    Thread.yield();
                }
            }
        };
        
        
        t1.start();
        
        //让主线程在恰当的时间去中断t1。
        Thread.sleep(1000);
        
        //此时t1处于sleep状态。interrupt() 方法会使t1中的sleep方法抛出InterruptedException异常。
t1.interrupt();

    }
}

打印输出:

Interruted When Sleep
Interruted!

7 suspend与resume

suspend() 方法表示将线程挂起。resume() 方法表示继续执行挂起的线程。

但需要注意suspend()不会释放锁。如果加锁发生在resume()之前,则发生死锁。

这两个方法都已经被废弃,不要使用。

例:运行以下程序,程序将被锁死。

packagesjq.thread.suspend_resume;
packagesjq.thread.suspend_resume;

public classSuspendResumeThreadTest {
    
    public static Object u = newObject();
    public static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    public static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    
    public static class ChangeObjectThread extendsThread {
        publicChangeObjectThread(String name){
            super.setName(name);
        }

        @Override
        public voidrun() {
            synchronized(u){
                System.out.println("in " + super.getName());
                Thread.currentThread().suspend();
            }
        }
        
    }
    
    public static void main(String[] args) throwsInterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();  //主线程通知t1要释放resume,但t1线程可能还未执行suspend,当t1执行了suspend后,则t1将永远被挂起。且不会释放资源。
        t2.resume();  //主线程通知t2要释放resume,但t2线程可能还未执行suspend,当t2执行了suspend后,则t2将永远被挂起。且不会释放资源。
t1.join();
        t2.join();
    }
}

控制台显示:

Java 并发——多线程基础第3张

该程序通过jstack查看线程情况,发现t2线程处于RUNNABLE状态,被suspend0方法挂起。且t2拥有Object的锁,只要t2线程不结束,Object的锁就不会被释放。

Java 并发——多线程基础第4张

免责声明:文章转载自《Java 并发——多线程基础》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇如何在ios手机端的Safari浏览器 中“查看网页源代码”获取Cookie 遇到的问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Delphi源程序格式书写规范

Delphi源程序格式书写规范     1,规范简介   本规范主要规定Delphi源程序在书写过程中所应遵循的规则及注意事项。编写该规范的目的是使公司软件开发人员的源代码书写习惯保持一致。这样做可以使每一个组员都可以理解其它组员的代码,以便于源代码的二次开发记忆系统的维护。 2,一般格式规范 2.1 缩进   缩进就是在当源程序的级改变时为增加可读性而露...

Android Volley框架的几种post提交请求方式

首先简单描写叙述一下Google的Android开发团队在2013年推出的一个网络通信框架Volley.它的设计目标是进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比方下载文件等,Volley的表现就不尽如人意。 在app开发中,我们最常见的就是从appclient向服务端发一个http请求.对于两种主要的web请求方式get和post...

java栈、堆

一。栈、堆几个小概念 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。) 3. 堆:存放所有new出来的对象。 4. 静态域 :存放静态成员(static定义的) 5. 常量池 :...

statement 、prepareStatement的用法和解释

转自:http://blog.csdn.net/QH_JAVA/article/details/48245945 一、prepareStatement 的用法和解释 1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Stateme...

jvm锁的四种状态 无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态

一:java多线程互斥,和java多线程引入偏向锁和轻量级锁的原因? --->synchronized是在jvm层面实现同步的一种机制。    jvm规范中可以看到synchronized在jvm里实现原理,jvm基于进入和退出Monitor对象来实现方法同步和代码块同的。在代码同步的开始位置织入monitorenter,在结束同步的位置(正常结束和...

基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)

作者:彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台tiny4412 ADK Linux-4.9   概述前面几篇博文列举了在有设备树的时候,gpio中断的用法示例。下面我们尝试分析一下Linux内核是如何做到的,如果哪写的有问题,欢迎大家批评指正,谢谢。 还是以GPIO中断为例分析,对于tiny4412,gpi...