Java-线程

1、程序,进程和线程

程序:程序(program)是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码

进程:进程((process)是正在执行的程序,从Windows角度讲,进程是操作系统进行资源分配的最小单位.

线程:线程(thread)进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程

2、进程和线程的关系

  • 一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
  • 每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main()方法就是在主线程中被执行的
  •  在主线程中可以创建并启动其它的线程
  • 一个进程内的所有线程共享该进程的内存资源

3、如何创建线程

3.1、第一种方法:继承Thread类

  •  在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下:
  •  Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
// 定义
public class MyThread extends Thread {
     public void run() {
     }
}
// 调用
MyThread thread = new MyThread();
thread.start();

例:

/*
实现线程的第一种方式:
   编写一个类,直接继承java.lang.Thread,重写run方法。

   怎么创建线程对象?  new就行了
   怎么启动线程呢?  调用线程对象的start()方法

   方法体中的代码永远是一次自上而下的顺序逐行进行的
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        // 这里是main()方法,这里的代码属于主线程,在主线程中运行
        // 新建一个分支线程
        MyThread myThread = new MyThread();

        // myThread.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程)不是并发的

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

        // 这里的代码还是运行在主线程中
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程-->" + i);
        }
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        // 编写程序。这段程序运行在分支线程中(分支线)
        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程-->" + i);
        }
    }
}

3.2、第二种方法:实现Runnable接口

  • 避免了单继承的局限性,多个线程可以共享同一个接口实现类的对象非常适合多个相同线程来处理同一份资源。
  •  java.lang.Runnable接口中仅仅只有一个抽象方法
public interface Runnable {
    public abstract void run();
}

例: 

/*
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        // 创建一个可运行的对象
        // MyRunnable r = new MyRunnable();
        // 将一个可运行的对象,封装为一个线程对象
        // Thread t = new Thread(r);

        // 合并
        Thread t = new Thread(new MyThread());

        // 启动线程
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程-->" + i);
        }
    }
}
// 这并不是一个线程类,是一个可运行的类,他还不是一个线程
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程-->" + i);
        }
    }
}

 3.3、第三种方式:实现Callable接口:

  • 优点:可以获取到线程的执行结果
  • 缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻,效率较低

例:

/*
实现线程的第三种方式:实现Callable接口:
 */
public class ThreadTest14 {
    public static void main(String[] args) throws Exception{

        // 第一步:创建一个匿名内部类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法相当于run方法,只不过这个有返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟换行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; // 自动装箱(300结果编程Integer)
            }
        });

        // 创建线程对象
        Thread t = new Thread(task);

        // 启动线程
        t.start();

        // 直立式main方法,是在主线程中,
        // 在主线程中,怎么获取线程返回结果。
        // get()方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);

        // main方法这里的程序要想缓慢执行必须等待get()方法的结果
        // 而get()方法可能会需要很久,因为get()方法是为了拿另一个现成的执行结果
        // 另一个县城是需要时间的。

        System.out.println("helloo world");
    }
}

3.4、第四种方式:通过 Callable 和 Future 创建线程

  • 相比run()方法可以有返回值,方法可以抛出异常,支持泛型的返回值,但需要借助FutureTask类获取返回结果

3.5、 继承方式和实现方式的联系与区别

 区别:

  • 继承Thread: 线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法

4、Thread类中的常用方法

方法说明
void start()
启动线程
void setName()设置线程的名称
final String getName()
返回线程的名称
final void setPriority(int newPriority)
设置线程的优先级
final int getPriority()
返回线程的优先级
final void join()
等待线程终止
static Thread currentThread()
返回对当前正在执行的线程对象的引用
static void sleep(long millis)
使线程休眠 以毫秒数暂停
yield()
对程序调度的暗示,即线程让步

5、线程优先级

  • 事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
  • 优先级较高的线程有更多获得CPU的机会,反之亦然;
  • 优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级
  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序
  • 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

Thread类有如下3个静态常量来表示优先级

  • MAX_PRIORITY:取值为10,表示最高优先级。  
  • MIN_PRIORITY:取值为1,表示最底优先级。
  • NORM_PRIORITY:取值为5,表示默认的优先级。

线程优先级测试用例:

public class ThreadTest10 {
    public static void main(String[] args) {
        // 设置主线程的优先级
        Thread.currentThread().setPriority(1);

        /*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()); // 5

        // 创建实例方法
        /*MyRunable06 my = new MyRunable06();
        Thread t = new Thread(my);
        t.start();*/

        Thread t = new Thread(new MyRunable06());

        t.setName("tt");
        t.setPriority(10);
        t.start();

        // 优先级较高的,只是见到的CPU时间片相对较多一些
        // 大概率方向偏向于优先级较高的
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
class MyRunable06 implements Runnable{

    @Override
    public void run() {
        /*// 获取线程优先级
        System.out.println(Thread.currentThread().getName() + "线程的默认优先级" + Thread.currentThread().getPriority()); //5*/
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

 

6、线程状态

线程在他的生命周期中会处于不同的状态,大致分为下面几个:

  • 新建:使用Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态,保持这个状态直到start()线程。
  • 就绪:当线程对象调用了start()方法以后,该线程就进入就绪状态。在就绪队列等待调度。
  • 运行:当就绪的线程获取了CPU资源,就可以执行run(),此时线程处于运行状态。此时可以变成阻塞状态,就绪状态和死亡状态。
  • 阻塞:如果一个线程执行了sleep()方法,失去所占用的资源后,就从运行状态变为阻塞状态。在睡眠时间已到或者重新获得资源后进入就绪状态。
  • 死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

7、守护线程

 Java中的线程分为两类:用户线程和守护线程

  • 任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作
  • 只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
  • 守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
  • 用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没 有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了.
  • 设置守护线程: setDaemon(boolean on)
注意 : 设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。

8、多线程

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
优点:(1)提高CPU利用率(2)提高程序响应(3)改善程序结构,将复杂任务分为多个线程,独立运行
缺点:(1)线程也是内存,线程越多占用内存也越多(2)多线程需要协调和管理,所以需要CPU时间跟踪线程(3)线程之间对共享资源的访问会相互影响

9、线程同步

 多个线程同时要读写一份共享资源时,可能会发生冲突,所以才引入线程同步。

9.1、并发与同步 

并行:多个CPU同时执行多个任务 

并发:在一个时间段依次执行操作

同步:就是排队+锁

  • 多个线程直接要进行排队,一个一个对共享资源进行操作。为了保证数据正确性,在访问时加入锁,确保一个时间点只有一个线程访问共享资源。

如何实现同步:使用关键字synchronized(同步锁)同步方法或代码块

同步锁:同步锁可以是任何对象,必须为一,保证多个线程获得的是同一个对象(用来充当锁标记)

/*
守护线程
 */
public class ThreadTest13 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        t.setName("备份数据的线程");

        // 自动线程之后,将线程
        t.setDaemon(true);

        t.start();

        // 主线程、主线程是用户线程
        for (int i = 0; i < 10 ; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BakDataThread extends Thread{
    public void run(){
        int i = 5;
        // 即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止
        while(true){
            System.out.println(Thread.currentThread().getName() + "-->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值