多线程(一)

1 多线程简介

介绍多线程之前要介绍线程,介绍线程则离不开进程。

  • 进程:是一个正在执行中的程序,例如视频软件看电影。
  • 线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。例如:看电影时的声音、字幕、图像。
  • 多线程:一个进程中不只有一个线程,例如看电影时的声音、字幕、图像同时进行。

 为什么要用多线程:

  • 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行而使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;(电影放映的时候总不能把声音、字幕、图像分开播放吧)
  • 进程之间不能共享数据,线程可以;
  • 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小
  • Java语言内置了多线程功能支持,简化了java多线程编程。

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即一个cpu的情况下,在同一个时间点, cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

2 线程的创建   三种方式

2.1 继承Thread类

步骤:
① 定义类继承Thread;
② 复写Thread类中的run()方法;
③ 调用线程的start()方法。

public class ThreadDemo1 {
    public static void main(String[] args) {  
        //创建两个线程
        ThreadDemo td = new ThreadDemo("zhangsan");
        ThreadDemo tt = new ThreadDemo("lisi");
        //执行多线程特有方法,如果使用td.run();也会执行,但会以单线程方式执行。
        td.start();
        tt.start();
        //主线程
        for (int i = 0; i < 5; i++) {
            System.out.println("main" + ":run" + i);
        }
    }
}
//继承Thread类
class ThreadDemo extends Thread{
    
    //设置线程名称
    ThreadDemo(String name){
        super(name);
    }
    //重写run方法。
    public void run(){
        for(int i = 0; i < 5; i++){
        System.out.println(this.getName() + ":run" + i);  //currentThread()  获取当前线程对象(静态)  getName() 获取线程名称。
        }
    }
}

优势:编写简单,可直接用this.getName()获取当前线程,不必使用Thread.currentThread()方法。
劣势:已经继承了Thread类,无法再继承其他类。

2.2 实现Runnable接口

步骤:
① 定义类实现Runnable接口;
② 重写run方法,将线程要运行的代码放在该run方法中;
③ 通过Thread类建立线程对象;
④ 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;
⑤ 调用Thread类的start方法开启线程。

public class RunnableDemo {
    public static void main(String[] args) {
        RunTest rt = new RunTest();
        //建立线程对象
        Thread t1 = new Thread(rt);
        Thread t2 = new Thread(rt);
        //开启线程并调用run方法。
        t1.start();
        t2.start();
    }
}
//定义类实现Runnable接口
class RunTest implements Runnable{
    private int tick = 10;
    //覆盖Runnable接口中的run方法,并将线程要运行的代码放在该run方法中。
    public void run(){
        while (true) {
            if(tick > 0){
                System.out.println(Thread.currentThread().getName() + "..." + tick--);
            }
        }
    }
}

优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

2.3 通过Callable和Future创建线程(了解)

步骤:
①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值;
②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值;
③、使用FutureTask对象作为Thread对象启动新线程;
④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

public class CallableFutrueTest {
    public static void main(String[] args) {
        CallableTest ct = new CallableTest();                        //创建对象
        FutureTask<Integer> ft = new FutureTask<Integer>(ct);        //使用FutureTask包装CallableTest对象
        for(int i = 0; i < 100; i++){
            //输出主线程
            System.out.println(Thread.currentThread().getName() + "主线程的i为:" + i);
            //当主线程执行第30次之后开启子线程
            if(i == 30){        
                Thread td = new Thread(ft,"子线程");
                td.start();
            }
        }
        //获取并输出子线程call()方法的返回值
        try {
            System.out.println("子线程的返回值为" + ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class CallableTest implements Callable<Integer>{
    //复写call() 方法,call()方法具有返回值
    public Integer call() throws Exception {
        int i = 0;
        for( ; i<100; i++){
            System.out.println(Thread.currentThread().getName() + "的变量值为:" + i);
        }
        return i;
    }
}

优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法。

3 线程的状态  五大状态

  • 新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态。
  • 就绪 :线程对象调用start()方法后,就处于就绪状态,等待JVM里的线程调度器的调度
  • 运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。
  • 等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态
  • 终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。

4 线程的方法

4.1 线程睡眠---sleep

线程睡眠的原因:线程执行的太快,模拟网络延时,或需要强制执行到下一个线程。
线程睡眠的方法:sleep(long millis),在指定的毫秒数内让正在执行的线程休眠。
线程睡眠的代码演示:

public class SynTest {
    public static void main(String[] args) {
        new Thread(new CountDown(),"倒计时").start();
    }
}
class CountDown implements Runnable{
    int time = 10;
    public void run() {
        while (true) {
            if(time>=0){
                System.out.println(Thread.currentThread().getName() + ":" + time--);
                try {
                    Thread.sleep(1000); //睡眠时间为1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
/*result:
倒计时:10
倒计时:9
倒计时:8
倒计时:7
倒计时:6
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1
倒计时:0
*/

注意:由于使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

4.2 线程让步---yield

该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。
线程让步的代码演示:

public class SynTest {
    public static void main(String[] args) {
        yieldDemo ms = new yieldDemo();
        Thread t1 = new Thread(ms,"张三吃完还剩");
        Thread t2 = new Thread(ms,"李四吃完还剩");
        Thread t3 = new Thread(ms,"王五吃完还剩");
        t1.start();
        t2.start();
        t3.start();
    }
}
class yieldDemo implements Runnable{
    int count = 20;
    public void run() {
        while (true) {
                if(count>0){
                    System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
                    if(count % 2 == 0){
                        Thread.yield();                  //线程让步
                    }
            }
        }
    }
}

sleep和yield的区别:
① sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
② sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。

4.3 线程合并---join

当B线程执行到了A线程的join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
join()可以用来临时加入线程执行。
以下为代码演示:

public static void main(String[] args) throws InterruptedException {    
        yieldDemo ms = new yieldDemo();
        Thread t1 = new Thread(ms,"张三吃完还剩");
        Thread t2 = new Thread(ms,"李四吃完还剩");
        Thread t3 = new Thread(ms,"王五吃完还剩");
        t1.start();
        t1.join();
        
        t2.start();
        t3.start();
        System.out.println( "主线程");
    }

4.4  停止线程

原stop方法因有缺陷已经停用了,那么现在改如何停止线程?现在分享一种,就是让run方法结束。
开启多线程运行,运行的代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
具体代码如下:

public class StopThread {

    public static void main(String[] args) {
        int num = 0;
        StopTh st = new StopTh();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t1.start();
        t2.start();
        //设置主线程执行50次,执行结束之后停止线程
        while (true) {
            if(num++ == 50){                        
                st.flagChange();
                break;
            }
            System.out.println(Thread.currentThread().getName() + "..." + num);
        }
    }
}

class StopTh implements Runnable{

    private boolean flag = true;
    public void run() {
        while(flag){
            System.out.println(Thread.currentThread().getName() + "stop run" );
        }
    }
    public void flagChange(){
        flag = false;
    }
}

4.5 其他方法

5 线程的优先级
  每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
  Thread类中提供了优先级的三个常量,代码如下:
MAX_PRIORITY   =10

MIN_PRIORITY   =1

NORM_PRIORITY   =5
------------------------------------------------------
 ThreadDemo td = new ThreadDemo();
 Thread t1 = new Thread(td,"张三");
 t1.priority(9);            //设置优先级
 t1.start();              //设置完毕

 

posted @ 2019-03-16 19:12  糖醋小瓶子  阅读(132)  评论(0编辑  收藏  举报