多线程学习(一)

一、创建线程的三种方式

1. 继承Thread类,重写run()方法
  1. 代码展示
public class myThread extends Thread {
	//成员变量
    private String title;
    //构造方法
    public myThread(String title){
        this.title=title;
    }
    //业务实现
    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println(title+"运行,x="+ i);
        }
    }

    public static void main(String[] args) {
        myThread t1=new myThread("线程1");
        myThread t2=new myThread("线程2");
        myThread t3=new myThread("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

注意:

  • 启动线程需要调用start()方法
  • 同一线程对象只能调用一次start()方法,否则会抛出IllegalThreadStateException
  • 如果想调用对象的run()方法来启动线程,则代码只会按序输出并不会启动多线程
  • 如果获取当前线程无需调用Thread.currentThread(),直接使用this获得即可。
  1. 为什么是start()方法
    贴一下源代码:
    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

    private native void start0();

分析:
  这里主要介绍以下start0()方法,源码中可以看到该方法使用native修饰,这意味着它可以调用我们所使用机器的操作系统的底层函数,这项技术实际上是JNI(Java native interface)。
 在这里插入图片描述
  由于不同操作系统对于资源分配的算法不同,所以为了满足不同层次的开发者,Java提供了start0()方法,当编译好的字节码文件在jvm中运行时,jvm使用JNI技术调用当前操作系统的底层函数用来匹配start0()方法,进而实现了线程在机器上的调度。
 从编程的角度来看,Java中的多线程必须使用Thread类中的start()方法启动。

2.继承Runnable接口,实现run()方法

 
  在Java中受限于单继承的限制,所以在有些时候使用继承Thread是不合适的。这时我们就可以通过实现Runnable接口的方式进行多线程编程。代码如下:

public class MThread implements Runnable{
    private String title;
    public MThread(String title){
        this.title=title;
    }
    //实现run()
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println(title+"运行,x="+ i);
        }
    }
    //启动多线程
    public static void main(String[] args) {
	    Thread t1=new Thread(new MThread("线程1"));
	    t1.start();
    }
}

注意:

  • 前面我们提到过启动多线程必须使用Thread类中的start()方法,而Runnable接口中并没有提供start()方法,这时候我们就必须构造一个Thread类。下面看一段源代码:
	//Thread的构造方法
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
	//Thread的run方法
    public void run() {
        if (target != null) {
            target.run();
        }
    }

可以看到,Thread中的run()方法实际上是调用的Runnable对象中的run()方法。当我们把自己创建的实现Runnable接口的对象传入构造方法时,我们可以得到一个等价于复写父类中run()方法的效果。

  • 实际上,Java多线程的设计使用了代理模式的设计。Thread类是一个代理类,开发者只需要关注如何实现run()方法。
  • Thread类描述线程,实现Runnable接口的子类用于描述资源。下面代码展示了一个售票程序的简单代码。即多个线程访问同一资源。
//实现runnable的子类,描述资源
public class MThread implements Runnable{
    private int tickets=5;
    private String saler;
    public MThread(String saler){
        this.saler=saler;
    }
    //实现run()
    public void run() {
        for(int i=0;i<100;i++){
            if(tickets>0) {
                System.out.println(saler + " 卖票,票还剩余" + tickets--);
            }
        }
    }
}

public class myThread {
	//启动多线程,Thread描述线程
    public static void main(String[] args) {
        MThread mt=new MThread("多线程");
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }

}

上述程序的内存结构图如下:
在这里插入图片描述##### 3.继承Callable接口
在这里插入图片描述

//实现callable的子类
public class MThread implements Callable<String> {

    public String call() throws Exception {
        for(int i=0;i<5;i++){
            System.out.println("************售票************");
        }
        return "售罄";
    }
}
//启动多线程
public class myThread {

    public static void main(String[] args) throws Exception{
        FutureTask<String> task=new FutureTask<String>(new MThread());
        Thread t=new Thread(task);
        t.start();
        System.out.println("多线程执行完毕:"+task.get());
    }

}

注意:Runnable和Callable的区别

  • Runable是JDK1.0提出的多线程接口,Callable是JDK1.5之后
  • Callable接口中提供call()方法,并且提供返回值
  • Runnable中只提供run()方法,没有返回值

二、多线程的操作

Java中对于多线程的操作主要封装在Thread类中。

1.线程的命名和获取

Java对于线程的操作是基于线程名的。如果不设置名字系统会自动设置不重复的名字。

  • 构造方法设置名字:public Thread(Runnable target, String name)
  • 设置名字:public final synchronized void setName(String name)
  • 获取名字:public final String getName()
2.线程休眠

线程休眠是指是线程暂缓一段时间执行,过了规定的时间,线程被自动唤醒。

  • public static native void sleep(long millis) throws InterruptedException;
  • public static void sleep(long millis, int nanos) throws InterruptedException
    注意:
      这两个方法都会抛出中断异常(InterruptedException),这个异常是Exception的子类,不是RuntimeException的子类,所以需要手动处理。
3.线程中断

在之前线程休眠可以抛出中断异常,这就提示我们线程的休眠是可以被中断的。

  • 判断线程是否被中断:public boolean isInterrupted()
  • 中断线程:public void interrupt()
4.线程的强制执行

一直占用资源直至线程任务执行完毕。
public final void join() throws InterruptedException

5.线程的礼让

调用该方法的线程会自动让出当前占用的资源。
public static native void yield()

6.线程的优先级
  • 设置优先级:public final void setPriority(int newPriority)
  • 获取优先级:public final int getPriority()
  • 静态常量:Thread.MAX_PRIORITY10、Thread.NORM_PRIORITY5、Thread.MIN_PRIORITY1
  • 线程的默认优先级是Thread.NORM_PRIORITY5

关于三种方式创建多线程得小结:
采用实现Runnable、Callable接口方式创建多线程的优缺点:

  1. 线程类只是实现了接口,还可以继承其他类。
  2. 这种方式下多个线程可以共享同一个target对象,所以非常适合多个相同线程处理同一份资源的情况。
  3. 缺点是编程稍复杂,访问当前线程需要调用调用Thread.currentThread()
    采用继承Thread类创建多线程的优缺点:
  4. 优点是编写简单,访问当前线程直接使用this即可
  5. 劣势是无法继承其他父类

关于sleep()和yield()方法的区别:

  1. sleep()方法暂停当前线程后会个其他线程(无论优先级高低)执行机会,而yield()方法暂停当前线程后,只有优先级大于或等于当前线程的就绪线程才能执行。
  2. sleep()方法会使当前线程进入阻塞状态,只有在过了规定时间后才能进入就绪状态。yield()方法会使当前线程进入就绪态。
  3. sleep()方法声明抛出了异常,yield()方法没有。
  4. sleep()方法有更好的可移植性,推荐用sleep()方法控制并发线程的执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值