java多线程

概述:

程序、进程、线程的理解:

在这里插入图片描述

总结:
1.程序:是为了完成特定任务、用某种语言编写的一组指令集合。即指一段静态的代码
2.进程:正在内存中运行的一个程序即进程。进程也是程序的一次执行过程。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的独立的内存区域。

区分程序和进程:放在硬盘中的静态代码叫程序,放在内存中运行的代码叫进程。

3.线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

总结:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

程序,进程,线程的举例:比如360安全卫士,我们打开360安全卫士/腾讯电脑管家等,看任务管理器,发现运行的就是进程(正在运行的程序,即进程),我们既可以用360程序体检,同时可以杀毒,同时可以电脑清理,系统修复等。而我们可以认为体检、杀毒、清理,系统修复等是一个个线程。

如下图演示:
在这里插入图片描述

线程调度的方式:

1.分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间。
2.抢占式调度:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程随机性),注意java采用的是抢占式调度。

还可以通过如下图方式设置线程的优先级:
在这里插入图片描述

线程抢占式调度详情:

在这里插入图片描述

jvm内存结构图:

在这里插入图片描述
上图说明:
1.进程可以细化为多个线程。
2.每个线程,拥有自己独立的:栈(即虚拟机栈),程序计数器。
3.多个线程,共享同一个进程中的结构:方法区、堆。

并行与并发:

形象理解并发和并行:
并发相当于闪电侠(相当于一个cpu),并行相当于鸣人的影分身(多个cpu,或者多核cpu),他们两个人都同时 画两幅画,闪电侠快速在两幅画之间切换着画,而鸣人直接开启影分身 同时分别 在两幅画上作画。

并发:在一个时间段内发生若干事件;单核cpu同时执行多个任务时,时间片进行很快的切换,线程轮流执行cpu。比如一个人一个时间段内做不同的事。

并行:在同一时刻发生若干事件;多核cpu同时执行多个任务。比如,高速公路8车道同时通过好几辆车,秒杀。

如下图演示:
在这里插入图片描述

总结:

所以,并发是在一段时间内宏观上多个程序同时运行,并行是在某一时刻,真正有多个程序在运行。

并行和并发的区别:
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。

并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的、

只有在多CPU或者一个CPU多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。

例如使用单核CPU,多个工作任务是以并发方式运行的,因为只有一个CPU,各个任务分别占用一段时间,再切换到其他任务,等到下一次CPU使用权是再次执行未完成的任务。

使用多核CPU时,可以将任务分配到不同的核同时运行,实现并行。

在这里插入图片描述



多线程概述:

多线程概念:
多线程是指,将原本线性执行的任务分开成若干个子任务同步执行,这样做的优点是防止线程“堵塞”,增强用户体验和程序的效率。缺点是代码的复杂程度会大大提高,而且对于硬件的要求也相应地提高 。

主线程:执行主方法(main方法)的线程。
单线程程序:java程序只有一个线程。执行从main方法开始,从上到下依次执行。


为什么用多线程?

多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?

多线程的好处:

1.方便的通信和数据交换。

2.充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力


多线程的优点:

多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对于多进程程序结构有以下的优势:

1、方便的通信和数据交换
线程间有方便的通信和数据交换机制。对于不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便 。

2、更高效地利用CPU
使用多线程可以加快应用程序的响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,就可以避免这种尴尬的情况 。

同时,多线程使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

在这里插入图片描述


何时使用多线程?

1.当我们处理一个消耗大的任务(如上传或下载图片),如果让主线程执行这个任务,它会等到动作完成,才继续后面的代码。在这段时间之内,主线程处于“忙碌”状态,也就是无法执行任何其他功能。体现在界面上就是,用户的界面完全“卡死”,这时候就需要另外一个单独开辟的路径(多线程)去执行下载上传图片。

在这里插入图片描述
在这里插入图片描述

多线程执行的内存图解:

当每次创建一个线程的时候,都会开辟新的栈空间,运行其run方法。进行压栈和出栈操作。

在这里插入图片描述


上下文切换

多核cpu下,多线程是并行工作的,如果线程数多(一个进程运行的任务多,开辟的线程多),单个核又会并发的调度线程,运行时会有上下文切换的概念

cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。

线程的cpu时间片用完
垃圾回收
线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。



多线程的创建:

在这里插入图片描述

多线程的创建方式一:继承Thread类

1.创建一个继承Thread类的子类。
2.重写Thread类的run()方法—>将此线程执行的操作声明在run()中。
3.创建Thread类的子类对象。(在主线程中做)
4.通过此对象调用start().

代码演示:

package com.fan.thread1;
//1.创建一个继承于Thread类的子类
 class MyThread extends Thread {

    //2.重写Thread类的run方法
    @Override
    public void run() {
        //我们自己写的要执行的逻辑
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class MyThreadTest{
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4. 通过此对象调用start()
        t1.start();

        //问题一:我们不能通过直接调用run()的方式启动线程,只能普通调用run方法
        //t1.run();

        /*问题二:再启动一个线程,遍历100以内的偶数,不可以
                还让已经start()的线程去执行。会报java.lang.IllegalThreadStateException异常
        */
        //t1.start();

        //两个for循环交替进行。两个线程并列运行。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i + "main()" + "****");
            }
        }

    }
}

代码解释:
t1是一个子线程,就好比孩子是从母亲肚子里出来的一样,此t1也是主线程帮我们造的。当我们调用t1.start()的时候,相当于t1自己独立去运行了。然后运行的是run里面的逻辑。此时程序中既有主线程,也有分线程,两者是并列的。孩子和母亲各自忙自己的。

说明:对象调用start()方法有两个作用:第一个是启动当前线程,第二个是调用当前线程的run方法。

第二个带main()" + “****” 的for循环是仍然在main线程中执行的。

问题一:
如果仅仅通过线程对象调用run方法,则仅仅是第二个是调用当前线程的run方法。 然而并没有启动线程。

问题二:再启动一个线程,遍历100以内的偶数,不可以还让已经start()的线程去执行。会报java.lang.IllegalThreadStateException异常。如果要启动多线程,那我们就创建多个线程对象并调用start方法

如图运行:注意此时主线程和子线程同时抢夺cpu的时间片段。所以谁先执行不确定
在这里插入图片描述

练习:
在这里插入图片描述
代码演示:

package com.fan.thread1;


public class MyThreadTest{
    public static void main(String[] args) {
        
       new Thread(){//创建Thread类的  匿名子类对象 并调用start方法
           @Override
           public void run() {
               for (int i = 0; i < 100; i++) {
                   if(i % 2 == 0){
                       System.out.println(Thread.currentThread().getName() + ":" + i);
                   }
               }
           }
       }.start();

       new Thread(){//创建Thread类的  匿名子类对象 并调用start方法
           @Override
           public void run() {
               for (int i = 0; i < 100; i++) {
                   if(i % 2 != 0){
                       System.out.println(Thread.currentThread().getName() + ":" + i);
                   }
               }
           }
       }.start();
    }
}

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

1.创建一个实现了Runnable接口的类。
2.实现类去实现Runnable中的抽象方法:run().
3. 创建实现类的对象。
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
5. 通过Thread类的对象调用start().

代码演示创建线程的第二种方式:

package com.fan.thread2;
/*1.创建一个实现了Runnable接口的类。
2.实现类去实现Runnable中的抽象方法:run().
3. 创建实现类的对象。
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
5. 通过Thread类的对象调用start().*/
public class RunnableTest {
    public static void main(String[] args) {
        //3. 创建实现类的对象。
        MyRunnable myRunnable = new MyRunnable();
        //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
        Thread t1 = new Thread(myRunnable);
        //5. 通过Thread类的对象调用start().
        t1.start();

        //再创建另外一个分的子线程
        Thread t2 = new Thread(myRunnable);
        t2.start();
    }
}

//1.创建一个实现了Runnable接口的类。
class MyRunnable implements Runnable {

    //2.实现类去实现Runnable中的抽象方法:run().
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                //注意:这里获取当前线程的名字不能通过直接getName()来实现(本类没有继承Thread类及其getName方法)
                // 本类实现的接口中全是抽象方法,根本就没有getName方法,所以我们要通过类调用静态方法的方式。
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

两种创建线程方式的比较:

开发中:优先选择:实现Runnable接口的方式。
原因:
1.实现的方式,没有类的单继承的局限性(可能本类还有一个自己的父类,这时候就不能再多继承线程类了)。
2.实现的方式更适合来处理多个线程有共享数据的情况(天然共享一份new Runnable()对象。当开多个线程的时候,参数都是同一份Runnable的子类对象。)。

联系:public class Thread implements Runnable

相同点:两种方式都是需要重写run(),将线程要执行的逻辑声明在run方法中。目前两种方式,想启动线程,都是调用的Thread类中的start();

多线程的创建方式三:实现Callable接口(JDK5.0新增的)

第一步:创建一个实现Callable的实现类。
第二步:实现call方法,将此线程需要执行的操作声明在calll()方法中。
第三步:创建Callable接口实现类的对象。
第四步:将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
第五步:将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start().
第六步:获取Callable中call方法的返回值。( Object o = futureTask.get();)

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

1.call()方法是可以有返回值的。
2.call()方法可以抛出异常,被外面的操作捕获,获取异常信息。
3.Callable是支持泛型的。

代码演示实 现Callable接口的方式创建多线程:

package com.fan.callable;

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

public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象。
        MyCallable myCallable = new MyCallable();

        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(myCallable);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start().
        Thread t1 = new Thread(futureTask);
        t1.start();


        try {
            //6.获取Callable中call方法的返回值。
            //get()返回值即为FutureTask构造器参数Callable实现类重写call()的返回值。
            Object o = futureTask.get();
            System.out.println("返回值总和为:" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.创建一个实现Callable的实现类
class MyCallable implements Callable {
    //实现call方法,将此线程需要执行的操作声明在calll()方法中。
    public Object call() throws Exception {
        int sum= 0;
        for (int i = 0; i <= 100 ; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

多线程的创建方式四:使用线程池(JDK5.0新增的)

举例,当我们用手机浏览一个旅游网或者图片多的网页(像一些赌博网站)的时候,一张图片下面配一段文字说明来描述图片。当我们往上滑的时候,就是一个线程帮我们在加载,然后每一个条目(item,这里假设是一张图片和一段文字),当我们上划一下的时候,就划过好几个条目,每一个条目需要一个线程去下载图片,我们这一页就需要很多线程去创建和销毁(当我们划走的时候),然后当我们一直上划的话,有些图片由于网速差还没下载下来我们就划走它了,这就造成了大量线程的创建和销毁。浪费cpu资源,提高响应速度等。

鉴于上述的资源消耗问题,我们会像使用我们的工具箱一样(重复利用原则),提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。(工具箱)。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

比如你要去北京天安门,你需要一辆车,然后发现没有车,你自己造了一辆自行车,到了天安门,使用完了,这个车你就销毁了,然后你就回不来了。(如果你在天安门乱喊,可能抓你。动不动有人查你身份证的)

在这里插入图片描述
在这里插入图片描述
代码演示线程池创建线程:

package com.fan.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread1());//适合于Runnable接口的实现类
        service.execute(new NumberThread2());

        //service.submit(Callable callable);//适合于使用Callable
        //关闭连接池/归还线程
        service.shutdown();
    }
}

class NumberThread1 implements Runnable{

    public void run() {
        //循环打印偶数的逻辑
        for (int i = 0; i <= 50; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
//另一个线程任务类
class NumberThread2 implements Runnable{

    public void run() {
        //循环打印偶数的逻辑
        for (int i = 0; i <= 50; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


Thread类的常用方法:

  1. start():启动当前线程;并调用当前线程的run方法。
  2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
  3. currentThread() :静态方法,返回执行当前代码的线程。
  4. getName() :获取当前线程的名字。
  5. setName() : 设置当前线程的名字。
  6. yield():线程的礼让方法, 释放当前cpu的执行权,让运行中的线程切换到就绪状态,重新争抢cpu的时间片。
  7. join(): 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态,继续执行。
  8. stop():已经过时,当执行次方法时,强制结束当前线程。
  9. sleep(long millitime):让当前运行中的线程“睡眠”指定的毫秒数。在指定的毫秒数时间内,当前线程是阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行
  10. isAlive():判断当前线程是否存活。

守护线程:

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程。

与守护线程相对的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序要完成的业务员操作。如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。

可以通过Thread.setDaemon设置守护线程。
注意:守护线程必须在start之前设置,否则会报错。

代码演示:

public class DaemonDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("i am alive");
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        System.out.println("finally block");
                    }
                }
            }
        });
        daemonThread.setDaemon(true);//设置成为守护线程
        daemonThread.start();
        //确保main线程结束前能给daemonThread能够分到时间片
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程的打断-interrupt()

// 相关方法的定义
public void interrupt() {
}
public boolean isInterrupted() {
}
public static boolean interrupted() {
}

isInterrupted() 获取线程的打断标记 ,调用后不会修改线程的打断标记.
打断标记:线程是否被打断,true表示被打断了,false表示没有

interrupt()方法用于中断线程

1.可以打断sleep,wait,join等显式的抛出InterruptedException方法的线程,但是打断后,线程的打断标记还是false(没有被打断)
2.打断正常线程 ,线程不会真正被中断,但是线程的==打断标记(isInterrupted)==为true(被打断了)

interrupt实例: 有个后台监控线程不停的监控,当外界打断它时,就结束运行。代码如下

@Slf4j
class TwoPhaseTerminal{
    // 监控线程
    private Thread monitor;

    public void start(){
        monitor = new Thread(() ->{
           // 不停的监控
            while (true){
                Thread thread = Thread.currentThread();
             	// 判断当前线程是否被打断
                if (thread.isInterrupted()){
                    log.info("当前线程被打断,结束运行");
                    break;
                }
                try {
                    Thread.sleep(1000);
                	// 监控逻辑中被打断后,打断标记为true
                    log.info("监控");
                } catch (InterruptedException e) {
                    // 睡眠时被打断时抛出异常 在该处捕获到 此时打断标记还是false
                    // 在调用一次中断 使得中断标记为true
                    thread.interrupt();
                }
            }
        });
        monitor.start();
    }

    public void stop(){
        monitor.interrupt();
    }
}

在这里插入图片描述
在这里插入图片描述
按快捷键 Alt+7就能显示当前类中的所有方法、全局常量,方法还包括形参和返回值,一目了然……打开界面如下:
在这里插入图片描述

代码演示线程常用方法(getName()、setName()、Thread.currentThread() ):

package com.fan.thread1;

import static java.lang.Thread.yield;

public class MyThread1Test {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        //给子线程起名字的方式一:
        t1.setName("子线程1:");
        t1.start();//从这里才进入子线程,其他代码全部是主线程执行的。

        //给子线程起名字的方式二:通过类的构造器,传一个String name 参数。
        /*MyThread1 t2 = new MyThread1("子线程2");
        t2.start();*/

        //同样,我们也可以给主线程起名字
        Thread.currentThread().setName("主线程:");
        //主线程中的其他代码。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

            if(i % 20 == 0){
                yield();//释放cpu的执行权
            }
        }
    }
}


class MyThread1 extends Thread{
    //子类无参构造
    public MyThread1() {
        super();
    }

    //子类有参构造调用父类有参构造
    public MyThread1(String name) {
        super(name);//我们直接去调用父类Thread的单参构造去起名字
    }

    @Override
    public void run() {
        //我们要让分线程帮我们运行的逻辑
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

join方法的代码演示:

join(): 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态,继续执行。

package com.fan.thread1;

public class MyThread1Test {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        //给子线程起名字的方式一:
        t1.setName("子线程1:");
        t1.start();//从这里才进入子线程,其他代码全部是主线程执行的。


        //同样,我们也可以给主线程起名字
        Thread.currentThread().setName("主线程:");
        //主线程中的其他代码。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

            if(i == 20 ){
                try {
                    t1.join();//t1加入了主线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        //判断线程是否存活
        System.out.println(t1.isAlive());
    }
}


class MyThread1 extends Thread{
    //子类无参构造
    public MyThread1() {
        super();
    }

    //子类有参构造调用父类有参构造
    public MyThread1(String name) {
        super(name);//我们直接去调用父类Thread的单参构造去起名字
    }

    @Override
    public void run() {
        //我们要让分线程帮我们运行的逻辑
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}

sleep(毫秒数):方法演示
在这里插入图片描述

线程的优先级:

MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 -->默认的优先级

2.如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级。
setPriority(int p):设置线程的优先级。
说明:高优先级的线程要抢占低优先级cpu的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。(只是高优先级被执行的概率高一点。就和中彩票一样)

在这里插入图片描述

线程的分类:

守护线程
为用户线程服务的线程:如垃圾回收,内存管理等线程。新建的线程最初都是用户级线程,可以通过setDaemon()方法设置成守护线程。
用户线程
一般用户使用的线程,通过继承Thread类后者实现Runnable接口等实现的线程。

在这里插入图片描述

线程的生命周期:

线程的5种状态:

1.新建状态:创建线程对象时的状态
2.可运行状态(就绪状态):调用start()方法后进入就绪状态,也就是准备好被cpu调度执行
3.运行状态:线程获取到cpu的时间片,执行run()方法的逻辑
4.阻塞状态: 线程被阻塞,放弃cpu的时间片,等待解除阻塞重新回到就绪状态争抢时间片
5.死亡状态: 线程执行完成或抛出异常后的状态

在这里插入图片描述

在这里插入图片描述
另外–线程的6种状态:

1.NEW(新建状态): 线程对象被创建,没调用start前。
2.Runnable(可运行状态): 可运行不一定正在运行,看是否争取到锁。线程调用了start()方法后进入该状态,该状态包含了三种情况
1)就绪状态 :等待cpu分配时间片
2)运行状态:进入Runnable方法执行任务
3)阻塞状态:BIO 执行阻塞式io流时的状态
3.Blocked(锁阻塞状态) 没获取到锁时的阻塞状态(同步锁章节会细说)
4.WAITING (无限等待)调用wait()、join()等方法后的状态,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
5.TIMED_WAITING (计时等待):同waiting状态,调用 sleep(time)、wait(time)、join(time)等方法后的状态
6.TERMINATED(被终止) 线程执行完成或抛出异常后的状态。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
java多线程机制: 例子 1 public class Example1 { static Lefthand left;static Righthand right; public static void main(String args[]) { left=new Lefthand(); //创建两个线程。 right=new Righthand(); left.start(); right.start(); } } class Lefthand extends Thread { public void run() { for(int I=0;I<=5;I++) { System.out.println("I am a student"); try{sleep(500);} catch(InterruptedException e){} } } } class Righthand extends Thread { public void run() { for(int I=0;I<=5;I++) { System.out.println("I am oookkk"); try{sleep(300);} catch(InterruptedException e){} } } } 在上述例子中,我们在main主线程中创建了两个新的线程lefthand和righthand。当lefthand调用start()开始运行时,类Lefthand中的run()将自动被执行。 我们来分析一下上面程序的输出结果。Left线程首先开始执行,这时Lefthand类中的run方法开始执行,输出”I am a student”后,left主动“休息”500毫秒,让出了CPU。这时正在排队等待CPU的right线程的run方法马上被执行,输出“I am ookk”,right在主动让出CPU300毫秒后又来排队等待CPU服务,这时right发现left还没有“醒来”,即没有来排队抢占CPU,因此left的run方法被执行,又输出“I am oookkk“… …。程序的执行结果是: E:\dd>java Example1 I am student I am oookkk I am oookkk I am student I am oookkk I am oookkk I am student I am oookkk I am student I am oookkk I am student I am student 2.实现Runnable接口 例子 2 import java.applet.*; import java.awt.*; public class Example2 extends java.applet.Applet implements Runnable { Thread circleThread; public void start() { if (circleThread==null) { circleThread=new Thread(this); circleThread.start(); } } public void run() { while(circleThread !=null) { repaint(); try{ circleThread.sleep(1000); } catch(InterruptedException e){} } } public void paint(Graphics g) { double i=Math.random(); if(i<0.5) g.setColor(Color.red); else g.setColor(Color.blue); g.fillOval(100,100,(int)(100*i),(int)(100*i)); } public void stop() { circleThread.yield(); circleThread=null; } } 在上述例子2中,我们在小程序这个主线程中用构造方法Thread(this)创建了一个新的线程。This代表着小程序作为这个新的线程的目标对象,因此我们的小程序必须为这个新创建的线程实现Runnable接口,即小程序利用Runnable接口为其中创建的这个新线程提供run()方法,给出该线程的操作。 首先,在小程序的start()方法中构造了一个名为circleThread的线程并调用线程类的start()方法来启动这一线程,即在小程序的主线程中又开始了一个线程:circleThread。下面的语句建立了一个新的线程: circlethread =new Thread(this); 其中this作为该线程的目标对象,它必须实现Runnable接口。线程被启动以后,自动调用目标对象的run()方法,除非线程被停止。在run()方法的第十一中,Applet重绘本身,然后睡眠1秒,同时要捕获异常事件并进行处理。 如果你离开这一页,程序将调用stop()方法,将线程置空。当你返回时,又会创建一个新的线程。在具体应用中,采用哪种方法来构造线程体要视具体情况而定。通常,当一个新的线程已继承了另一个类,而想在该线程中创建一个新的线程时,就应该用第二种方法来构造,即实现Rennable接口。 需要理解的是,我们的小应用程序实际上是浏览器的一个线程,这个线程由浏览器启动执行,浏览器自动调用执行小程中的init()、start()方法等。因此我们要创建一个新的线程最好把新线程的启动放在小程序的start()方法中。 下面的例子3是一个应用程序,这个应用程序在创建窗口的同时又创建了一个新的线程,该线程负责让窗口中的一个按钮改变它的大小。 例子 3 import java.awt.*; import java.awt.event.*; public class Example3 { public static void main(String args[]) { Mywin win=new Mywin(); win.pack(); } } class Mywin extends Frame implements Runnable { Button b=new Button("ok"); int x=5; Thread bird=null; Mywin() { setBounds(100,100,120,120); setLayout(new FlowLayout()); setVisible(true); add(b); b.setBackground(Color.green); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); bird=new Thread(this); //创建一个新的线程,窗口做目标对象, //替线程bird实现接口Runnable。 bird.start(); //在创建窗口时又开始了线程dird. } public void run() { while(true) { x=x+1; if(x>100) x=5; b.setBounds(40,40,x,x); try{bird.sleep(200);} catch(InterruptedException e){} } } } 滚动字幕线程。 例子 4 import java.applet.*; import java.awt.*; public class Example4 extends java.applet.Applet implements Runnable { int x=0; Thread Scrollwords=null; public void init() { setBackground(Color.cyan); setForeground(Color.red); setFont(new Font("TimesRoman",Font.BOLD,18)); } public void start() { if(Scrollwords==null) { Scrollwords=new Thread(this); Scrollwords.start(); } } public void run() { while (Scrollwords!=null) { x=x+5; if(x>500) x=0; repaint(); try{Scrollwords.sleep(80);} catch(InterruptedException e){} } } public void paint(Graphics g) { g.drawString("欢 迎 使 用 字 典 ",x ,80); } public void stop() { Scrollwords.yield(); Scrollwords=null; } } 带滚动字幕的小字典。 例子 5 import java.applet.*; import java.awt.*; import java.awt.event.*; public class Example5 extends Applet implements ActionListener,Runnable { TextField text1,text2; int x=0; Thread Scrollwords=null; public void init() { setBackground(Color.cyan); setForeground(Color.red); setFont(new Font("TimesRoman",Font.BOLD,18)); text1=new TextField(10); text2=new TextField(10); add(new Label("输入一个英文单词:")); add(text1); add(new Label("汉语意思:")); add(text2); text1.addActionListener(this); } public void start() { if(Scrollwords==null) { Scrollwords=new Thread(this); Scrollwords.start(); } } public void run() { while (Scrollwords!=null) { x=x+5; if(x>500) x=0; repaint(); try{Scrollwords.sleep(80);} catch(InterruptedException e){} } } public void paint(Graphics g) { g.drawString("欢 迎 使 用 字 典 ",x ,120); } public void stop() { Scrollwords.yield(); Scrollwords=null; } public void actionPerformed(ActionEvent e) { if((e.getSource()==text1)&&(text1.getText().equals("boy"))) { text2.setText("男孩"); } else if((e.getSource()==text1)&&(text1.getText().equals("sun"))) { text2.setText("太阳"); } else { text2.setText("没有该单词"); } } } 下面是一个左手画圆右手画方的例子。我们在主线程中创建了两个线程:left、right,其中一个负责画圆,另一个负责画方。在这个例子中我们使用了容器类的方法getGraphics()来获取一个Graphics对象(可以理解为一个画笔)。 例子 6 (效果如图1所示) 图1 双线程绘画程序 import java.applet.*; import java.awt.*; import java.awt.event.*; public class Example6 extends Applet implements Runnable { Thread left,right; Graphics mypen; int x,y; public void init() { left=new Thread(this); right=new Thread(this); x=10; y=10; mypen=getGraphics(); } public void start() { left.start(); right.start(); } public void run() { while(true) if (Thread.currentThread()==left) { x=x+1; if(x>240) x=10; mypen.setColor(Color.blue); mypen.clearRect(10,10,300,100); mypen.drawRect(10+x,10,50,50); try{left.sleep(60);} catch(InterruptedException e){} } else if(Thread.currentThread()==right) { y=y+1; if(y>240) y=10; mypen.setColor(Color.red); mypen.clearRect(10,110,300,100); mypen.drawOval(10+y,110,50,50); try{right.sleep(60);} catch(InterruptedException e){} } } public void stop() { left=null; right=null; } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值