j2se学习中的一些零碎知识点8之多线程

1、多线程的基本概念:

  • 线程是指进程中的一个执行场景,也就是执行流程。(每个进程就是一个应用程序,都有独立的内存空间;同一个进程中的线程共享其进程中的内存和资源。)
  • 多进程的作用:单进程使计算机只能够做一件事情。(如windows中的dos窗口命令行。)对于单核计算机,在同一时间点上,计算机的CPU只能够做一件事情,由于计算机在进程和进程之间频繁的进行切换,且切换速度极高,使人感觉多个进程在同时运行。(多进程的作用不是提高执行速度,而是为了提高CPU的使用率。)
  • 需要注意,进程与进程之间的内存是独立的。
  • 线程是一个进程中的执行场景,一个进程可以启动多个线程。多线程的作用:多线程不是为了提高执行速度,而是提高应用程序的使用率。由于计算机的执行速度非常快,可以给现实世界的人产生一种错觉,即多个线程在同时并发运行。(需要注意,java中的线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。)
    2、java程序的运行原理?
  • java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。如果没有启动其他线程,那就只有一个主线程在运行。
        package com.geeklicreed.j2se;
        /*
         * 分析以下程序有几个线程?
         * 以下程序只有一个线程,就是主线程
         * main,m1,m2,m3这四个方法都在同一个栈空间中
         * 没有启动其他任何线程。
         */
        public class ThreadTest {
            public static void main(String[] args){
                m1();
            }
            public static void m1(){
                m2();
            }
            public static void m2(){
                m3();
            }
            public static void m3(){
                System.out.println("m3...");
            }
        }

    3、线程的定义、创建和启动:(第一种方式)

  • java.lang.Thread类实现了java.lang.Runnable接口,Runnable接口中声明了run方法,Thread类中的run方法定义为:
    @Override
    public void run(){
    if(target != null){
        target.run(); //  targer引用为Runnable类型
    }
    }
  • run()方法在API文档中的说明:自定义的线程构造后会调用重写的run方法:(所以在自定义线程类时,需要继承Thread类,并重写Thread类中的run()方法)
  • 构造器Thread()方法创建一个线程,自定义的线程可以使用默认的构造器创建线程,默认的构造器在执行的第一行中会调用父类即Thread类的默认的构造器:
  • java.lang.Thread类中的start()方法的调用将使此线程开始执行,java虚拟机会调用此线程中的run()方法:
  • 示例代码:(需要注意的是,有了多线程之后,main方法结束只是主线程中没有方法栈帧,但是其他线程或者其他栈中还有栈帧,所以main方法结束,程序可能还在运行。)

        package com.geeklicreed.j2se;
    
        public class ThreadTest {
            public static void main(String[] args){
                //创建线程
                Thread t = new Processor();
    
                //启动
                t.start();  //  这段代码执行瞬间结束,告诉JVM再分配一个新的栈给t线程
    
                //这段代码在主线程中运行
                for(int i = 0; i < 10; i++){
                    System.out.println("main -->" + i);
                }
            }
        }
    
        //定义一个线程
        class Processor extends Thread{
            //重写run方法
            public void run(){
                for(int i = 0; i < 100; i++){
                    System.out.println("run -->" + i);
                }
            }
        }
  • 线程交叉运行:

    4、实现线程可以有第二种方式(自定义类实现java.lang.Runnable接口,并实现run()方法,比起自定义类直接继承Thread类来说,这种方式比较推荐,因为这个类实现接口之外保留了类的继承。)
  • 构造器Thread(Runnable target)方法可以传递Runnable类型的对象创建线程:

  • 示例代码:

        package com.geeklicreed.j2se;
        public class ThreadTest {
            public static void main(String[] args){
                //创建线程
                Thread t = new Thread(new Processor());
                //启动
                t.start();  
            }
        }
        //这种方式是推荐的,因为一个类实现接口以外保留了类的继承
        class Processor implements Runnable{
    
            @Override
            public void run() {
                for(int i = 0; i < 10; i++){
                    System.out.println("run -->" + i);
                }
            }
        }

    5、关于线程的生命周期:(状态图)

  • 解释上例ThreadTest03示例代码中线程生命周期的状态:首先java命令启动java虚拟机,java虚拟机的启动表示开启了一个应用程序(即进程),该进程会自动开启一个“主线程”,然后这个主线程会调用ThreadTest03类中的main方法,在方法中使用new关键字创建了t线程(新建状态),线程创建后调用start方法进入就绪状态,这个时候主线程和t线程一起同时抢夺CPU的时间片(时间片中的时间可长可短),首先抢夺到时间片的线程由java虚拟机调度自动运行run方法,当线程中时间片中的时间段结束时另一个线程中自动运行run方法。两个线程的时间片都结束后,两个线程又会重新会到就绪状态重新抢夺时间片,首先抢夺到时间片的线程继续执行run方法(接着执行,而不是重新执行),直到run方法运行结束线程消亡。期间可能发生阻塞事件导致线程进入阻塞状态,当阻塞解除时线程又会重新返回到就需状态。
    6、线程的调度与控制:
  • 通常我们的计算即只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。Java虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模式:分时调度模型和抢占式调度模型,Java使用抢占式调度模型。
  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
  • 抢占式调度模型:优先级高的线程获取的CPU时间片相对会多一些。如果线程的优先级相同,那么会随机选择一个。
  • 线程的优先级主要分为三种:Thread类中有三个常量,MAX_PRIORITY(10、最高级)、MIN_PRIORITY(1、最低级)和NORM_PRIORITY(5、默认)。
  • java.lang.Thread类中的currentThread()方法返回的是当前正在执行的线程对象:
  • java.lang.Thread类中的getName()方法返回的是线程对象的名字:(主线程默认的名字是main,其他线程的名字为Thread-编号)
  • java.lang.Thread类中的setName(String name)方法设置线程对象的名字:
  • java.lang.Thread类中可以通过getPriority()和setPriority(int newPriority)方法获取和设置线程优先级:


    7、关于sleep方法(线程休眠方法):Thread.sleep(毫秒),该方法的作用:阻塞当前线程,腾出CPU,让给其他线程。(进入阻塞状态)
  • 面试题:
        package com.geeklicreed.j2se;
            public class ThreadTest {
                public static void main(String[] args) throws Exception{
                    //创建线程
                    Thread t = new Processor();
                    t.setName("t");
                    //启动线程
                    t.start();
                    //休眠
                    t.sleep(5000); //等同于Thread.sleep(5000);阻塞的还是当前线程,和t线程无关
                    System.out.println("HelloWorld");
                }
            }
            class Processor extends Thread{
                @Override
                public void run() {
                    for(int i = 0; i < 200; i++){
                        System.out.println(Thread.currentThread().getName() + " --> " + i);
                    }
                }
            }
  • 某线程正在休眠,如何打断它的休眠:
  • java.lang.Thread类中的interrupt()方法打断线程对象的休眠:
                package com.geeklicreed.j2se;
                /*
                 * 某线程正在休眠,如何打断它的睡眠
                 */
                public class ThreadTest {
                    public static void main(String[] args) throws Exception{
                        //需求:启动线程,5秒后打断线程的休眠
                        Thread t = new Thread(new Processor());
                        //起名
                        t.setName("t");
                        //启动
                        t.start();
                        //5秒之后
                        Thread.sleep(5000);
                        //打断t的睡眠
                        t.interrupt();
                        /*
                            输出结果为:
                            t-->0
                            t-->1
                            t-->2
                            t-->3
                            t-->4
                            t-->5
                            t-->6
                            t-->7
                            t-->8
                            t-->9
                         */
                    }
                }
            class Processor implements Runnable{
                @Override
                public void run() {
                    try {
                        Thread.sleep(10000000000000L);
                        System.out.println("HelloWorld");
                    } catch (Exception e) {
                    }
                    for(int i = 0; i < 10; i++){
                        System.out.println(Thread.currentThread().getName() + "-->" + i);
                    }
                }
            }

    8、yield方法和线程合并:

  • java.lang.Thread类的静态yield()方法:会给同一个优先级的线程让位,但是让位时间不固定。和sleep方法相同,就是yield时间不固定。(yield是让位的意思)
  • java.io.Thread类中的join()方法表示合并线程,等待指定线程对象死亡后再执行在线程环境中的的代码。
                package com.geeklicreed.j2se;
                /*
                 *  线程的合并
                 */
                public class ThreadTest {
                    public static void main(String[] args) throws Exception{
                        Thread t = new Thread(new Processor());
                        t.setName("t");
                        t.start();
                        //合并线程
                        t.join(); //t和主线程合并,单线程的程序
                        //主线程
                        for(int i = 0; i < 10; i++){
                            System.out.println(Thread.currentThread().getName() + "-->" + i);
                        }
                        /*
                            输出结果为:
                            t-->0
                            t-->1
                            t-->2
                            t-->3
                            t-->4
                            main-->0
                            main-->1
                            main-->2
                            main-->3
                            main-->4
                            main-->5
                            main-->6
                            main-->7
                            main-->8
                            main-->9
                         */
                    }
                }
                class Processor implements Runnable{
                    @Override
                    public void run() {
                        for(int i = 0; i < 5; i++){
                            try {
                                Thread.sleep(1000);
                            } catch (Exception e) {
                            }
                            System.out.println(Thread.currentThread().getName() + "-->" + i);
                        }
                    }
                }

    9、线程的同步:

  • 异步编程模型和同步编程模型的区别?
    有两个线程t1和t2需要执行,t1线程执行t1,t2线程执行t2的,两个线程之间谁也不等谁,这是异步编程模型。t1线程和t2线程执行,当t1线程必须等t2线程执行结束之后,t1线程才能执行,这是同步线程模型。
  • 线程同步是为了数据安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。(线程同步机制使得程序变成【等同于】单线程)
  • 什么条件要使用线程同步?
    1、必须是多线程环境;2、多线程环境共享同一个数据;3、共享的数据涉及到修改操作。
  • 线程同步中的锁机制:(示例代码)

            package com.geeklicreed.j2se;
    
            /*
             *  以下程序使用线程同步机制保证数据的安全
             */
            public class ThreadTest {
                public static void main(String[] args) throws Exception {
                    //创建一个公共的账户
                    Account act = new Account("actno-001", 5000.0);
                    //创建线程对同一个账户取款
                    Thread t1 = new Thread(new Processor(act));
                    Thread t2 = new Thread(new Processor(act));
    
                    t1.start();
                    t2.start();
                    /*
                        输出结果为:
                        取款1000.0成功,余额:4000.0
                        取款1000.0成功,余额:3000.0
    
                     */
                }
            }
            // 取款线程
            class Processor implements Runnable {
                // 账户
                Account act;
                public Processor(Account act) {
                    super();
                    this.act = act;
                }
                @Override
                public void run() {
                    act.withdraw(1000.0);
                    System.out.println("取款1000.0成功,余额:" + act.getBalance());
                }
            }
            // 账户
            class Account {
                private String actno;
                private double balance;
                public Account(String actno, double balance) {
                    super();
                    this.actno = actno;
                    this.balance = balance;
                }
                public Account() {
                    // TODO Auto-generated constructor stub
                }
                public String getActno() {
                    return actno;
                }
                public void setActno(String actno) {
                    this.actno = actno;
                }
                public double getBalance() {
                    return balance;
                }
                public void setBalance(double balance) {
                    this.balance = balance;
                }
                // 对外提供一个取款的方法
                public void withdraw(double money) { // 对当前账户进行取款操作
                    // 把需要同步的代码,放到同步语句块中
                    synchronized (this) {
                        double after = balance - money;
                        // 延迟
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {
                        }
                        // 更新
                        this.setBalance(after);
                    }
                }
            }
  • 以上代码中的synchronized同步语句块原理解析:有t1线程和t2线程,t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,如果找到了this的对象锁,则进入同步语句块中执行程序,当同步语句块中的代码执行结束之后,t1线程归还this的对象锁。
    在t1线程执行同步语句块的过程中,如果t2线程也过来执行同步语句块中的代码,也会遇到synchronized关键字,所以也会去找this的对象锁,但是该对象锁被t1线程所持有,只能等待this对象锁的归还。(需要注意的是,synchronized关键字添加到成员方法上,线程拿到的也是this的对象锁。)
  • 需要注意的是,使用synchronized同步语句块的方式实现线程同步控制的代码越精细越好,因为单线程的执行程序比较消耗时间。
  • 面试题:
                package com.geeklicreed.j2se;
                public class ThreadTest {
                    public static void main(String[] args) throws Exception {
                        MyClass mc = new MyClass();
                        Processor p = new Processor(mc);
                        Thread t1 = new Thread(p);
                        t1.setName("t1");
                        Thread t2 = new Thread(p);
                        t2.setName("t2");
                        //启动线程
                        t1.start();
                        //延迟(保证t1线程先启动,并执行run)
                        Thread.sleep(1000);
                        t2.start();
                        /*
                            输出结果为:
                            m1....
                            m2...
                         */
                    }
                }
                class Processor implements Runnable{
                    MyClass mc;
                    Processor(MyClass mc){
                        this.mc = mc;
                    }
                    @Override
                    public void run() {
                        if(Thread.currentThread().getName().equals("t1")){
                            mc.m1();
                        }
                        if(Thread.currentThread().getName().equals("t2")){
                            mc.m2();
                        }
                    }
                }
                class MyClass{
                    public synchronized void m1(){
                        //休眠
                        try {
                            Thread.sleep(10000);
                        } catch (Exception e) {}
                        System.out.println("m1....");
                    }
                    /*
                    //m2方法的执行不需要等待m1的结束,因为m2方法上没有synchronized
                    public void m2(){
                        System.out.println("m2...");
                    }*/
                    //m2方法会等m1方法结束,t1,t2共享一个mc,并且m1和m2方法上都有synchronized
                    public synchronized void m2(){
                        System.out.println("m2...");
                    }
                }
  • 类锁:示例代码
            package com.geeklicreed.j2se;
            /*
             * 类锁,类只有一个,所以如果锁是类级别的,只有一个
             */
            public class ThreadTest {
                public static void main(String[] args) throws Exception {
                    Thread t1 = new Thread(new Processor());
                    Thread t2 = new Thread(new Processor());
                    t1.setName("t1");
                    t2.setName("t2");
                    t1.start();
                    //延迟,保证t1先执行
                    Thread.sleep(1000);
                    t2.start();
                    /*
                        输出结果为:
                        m1...
                        m2...
                     */
                }
            }
            class Processor implements Runnable{
                public void run(){
                    if("t1".equals(Thread.currentThread().getName())){
                        MyClass.m1();
                    }
                    if("t2".equals(Thread.currentThread().getName())){
                        MyClass.m2();
                    }
                }
            }
            class MyClass{
                //synchronized添加到静态方法上,线程执行到此方法的时候会找类锁
                public synchronized static void m1(){
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {}
                    System.out.println("m1...");
                }
                //不会等m1结束,因为该方法没有被synchronized修饰
                /*
                 `  public static void m2(){
                        System.out.println("m2...");
                 `  }
                 */
                //m2方法会等m1结束之后才能执行,该方法有synchronized
                //线程执行该代码需要“类锁”,而类锁只有一个
                public synchronized static void m2(){
                    System.out.println("m2...");
                }
            }
  • 死锁:示例代码
            package com.geeklicreed.j2se;
            public class ThreadTest {
                public static void main(String[] args) throws Exception {
                    Object o1 = new Object();
                    Object o2 = new Object();
                    Thread t1 = new Thread(new T1(o1, o2));
                    Thread t2 = new Thread(new T2(o1, o2));
                    t1.start();
                    t2.start();
                }
            }
            class T1 implements Runnable {
                Object o1;
                Object o2;
                T1(Object o1, Object o2) {
                    this.o1 = o1;
                    this.o2 = o2;
                }
                @Override
                public void run() {
                    synchronized (o1) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                        }
                        synchronized (o2) {
                        }
                    }
                }
            }
            class T2 implements Runnable {
                Object o1;
                Object o2;
                T2(Object o1, Object o2) {
                    this.o1 = o1;
                    this.o2 = o2;
                }
                @Override
                public void run() {
                    synchronized (o2) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                        }
                        synchronized (o1) {
                        }
                    }
                }
            }
  • 线程分类可以分为两种:用户线程(以上讲的都是用户线程),和守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束。例如java中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。(守护线程一般是无限执行的,除非其他所有的用户线程结束。)
  • java.lang.Thread类中的setDaemon(boolean on)方法标记当前的线程是作为一个用户线程还是守护线程。(该方法必须在线程调用start()方法之前被激活才有效。参数如果是true,则表示把当前线程设置为守护线程。)

    10、Timer定时器:java.util.Timer
  • 构造器Timer()方法创建一个定时器:
  • Timer类中的schedule(TimerTask task, Date firstTime, long period)方法指定定时任务:
  • 示例代码:
            package com.geeklicreed.j2se;
            import java.text.ParseException;
            import java.text.SimpleDateFormat;
            import java.util.Date;
            import java.util.Timer;
            import java.util.TimerTask;
            public class TimerTest {
                public static void main(String[] args) throws ParseException {
                    //1、创建定时器
                    Timer t = new Timer();
                    //2、指定定时任务
                    t.schedule(new LogTimerTack(), new SimpleDateFormat("yyy-MM-dd HH:mm:ss SSS").parse("2017-11-03 17:13:00 000"),
                            1000*60*60*24);
                    //打印结果为:
                    //2017-11-03 17:13:00 009
                }
            }
            //指定任务
            class LogTimerTack extends TimerTask{
                @Override
                public void run() {
                    System.out.println(new SimpleDateFormat("yyy-MM-dd HH:mm:ss SSS").format(new Date()));
                }
            }

    11、Object类中的wait()方法和notify()方法:

  • 导致当前线程等待,直到对象调用notify()或者是notifyAll()方法将该线程激活:
  • 唤醒单个线程,这个线程在对象监视器上等待:

转载于:https://blog.51cto.com/12402717/2045989

你可能感兴趣的:(j2se学习中的一些零碎知识点8之多线程)