Java多线程入门

1. 多线程概念

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2. 一个线程的生命周期

在这里插入图片描述
1、线程的5种状态:新建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)

线程变化的状态转换图例如以下:

1)新建状态(New):新创建了一个线程对象。

2)就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。

3)执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。

4)堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。

堵塞的情况分三种:

(一)等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。

(三)其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。

5)死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。
在这里插入图片描述
1)线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但无论如何,当我们new了这个对象后。线程就进入了初始状态;

2)当该对象调用了start()方法,就进入可执行状态;

3)进入可执行状态后,当该对象被操作系统选中。获得CPU时间片就会进入执行状态;

4)进入执行状态后情况就比较复杂了

(1)run()方法或main()方法结束后,线程就进入终止状态;

(2)当线程调用了自身的sleep()方法或其它线程的join()方法,就会进入堵塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后。该线程进入可执行状态,继续等待OS分配时间片;

(3)线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可执行状态,这时与其它进程处于同等竞争状态,OS有可能会接着又让这个进程进入执行状态;

(4)当线程刚进入可执行状态(注意,还没执行),发现将要调用的资源被synchroniza(同步),获取不到锁标记。将会马上进入锁池状态,等待获取锁标记(这时的锁池里或许已经有了其它线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可执行状态。等待OS分配CPU时间片;

(5)当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的全部资源,与堵塞状态不同)。进入这个状态后。是不能自己主动唤醒的,必须依靠其它线程调用notify()或notifyAll()方法才干被唤醒(因为notify()仅仅是唤醒一个线程,但我们由不能确定详细唤醒的是哪一个线程。或许我们须要唤醒的线程不可以被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池。等待获取锁标记。

3. 创建一个线程

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

3.1实现 Runnable 接口

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
实现了 Runnable 接口重写接口的run()方法

  • run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
    在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
  • 在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源)例如:
RunnableDemo runnableDemo = new RunnableDemo();
new Thread(runnableDemo).start();
  • 一旦调用start()方法,则会通过JVM找到run()方法。
  • 相较于通过继承Thread类来实现多线程,实现Runnable接口更加灵活:
  1. 避免点继承的局限,一个类可以继承多个接口。
  2. 使用Runnable实现多线程可以达到资源共享目的

3.2继承Thread类

创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。

  • 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
  • 该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

Thread 方法

1 public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name)
改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority)
更改线程的优先级。
5 public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
6 public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt()
中断线程。
8 public final boolean isAlive()
测试线程是否处于活动状态。

测试线程是否处于活动状态。 上述方法是被Thread对象调用的。
下面的方法是Thread类的静态方法

1 public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread()
返回对当前正在执行的线程对象的引用。
5 public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。

3.3通过 Callable 和 Future 创建线程

在这里插入图片描述

  • 实现Callable接口的好处:
  1. 可以定义返回值
  2. 可以抛出异常

4. 多线程的使用

有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。

请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

5. 线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是

1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

6. 停止线程

在这里插入图片描述

7. 线程休眠(阻塞)(sleep)

  • sleep (时间)指定当前线程阻塞的毫秒数;
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁, sleep不会释放锁

模拟倒计时

public class countdown implements Runnable {
    @Override
    public void run() {
        int count = 10;
        while (count >= 0) {
            try {
                Thread.sleep(1000);//线程阻塞1000ms=1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(count--);//每隔一秒打印秒数	
        }
    }
}

打印当前系统时间

public class SystemCurrentTime {
    public static void main(String[] args) throws InterruptedException {
        //获取系统当前时间
        Date startTime = new Date(System.currentTimeMillis());

        while (true) {
            Thread.sleep(1000);
            System.out.println("当前时间:"+new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime = new Date(System.currentTimeMillis());
        }
    }
}

8. 合并线程(Join)

  • 合并线程,优先执行该线程,替他线程进入阻塞状态,待此线程执行完成,在执行其他线程。
public class JoinOfThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我是VIP"+i);
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    JoinOfThread joinOfThread = new JoinOfThread();
    Thread thread = new Thread(joinOfThread);
    thread.start();

    //主线程
    for (int i = 0; i < 100; i++) {
        if (i == 80) {
            thread.join();//main线程阻塞,VIP插队执行
        }
        System.out.println("main" + i);
    }
}

9. 线程状态(Thread.State)

在这里插入图片描述

Thread thread = new Thread()//创建一个线程
Thread.State threadState = thread.getState();//获取线程当前状态

public class State{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程结束");
        });//创建一个线程
        //观察状态
        Thread.State threadState = thread.getState();//获取线程当前状态
        System.out.println(threadState);//NEW
        //启动线程
        thread.start();
        threadState = thread.getState();
        System.out.println(threadState);//Run

        while (threadState != Thread.State.TERMINATED) {//只要线程不停止,就一直输出状态
            Thread.sleep(100);
            threadState = thread.getState();//更新线程状态
            System.out.println(threadState);//输出状态
        }
    }
}

10. 守护线程(daemon)

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待…
//上帝(守护线程)
class God implements Runnable{
    @Override
    public void run() {
        while (true) {//模拟守护线程,线程自己不会结束
            System.out.println("上帝守护着你");
        }
    }
}
//人类(用户线程)
class Human implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("平凡的人类");
        }
        System.out.println("========goodbye========");
    }
}
//测试守护线程
//上帝守护着你
public class Daemon {
    public static void main(String[] args) {

        /**
         * 用户线程全部执行完成,守护线程才会结束
         */
        Thread god = new Thread(new God());
        //默认false表示用户线程,正常的线程都是用户线程..
        god.setDaemon(true);//设置God为守护线程
        god.start();//开启守护线程

        Thread human = new Thread(new Human());
        human.start();//启动用户线程

    }
}
  • 最终守护线程随着用户线程的死亡而结束
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值