Java第十五周作业

本周主题:Java多线程技术

目录

一、Java中的多线程概念

1、线程的生命周期

2、线程的优先级

3、线程的几个主要概念

二、多线程的使用

1、线程创建方法

2、线程的优先级

3、线程的同步机制

三、Java多线程售票小实例

1、错误示范

2、正确示范

3、总结:


一、Java中的多线程概念


Java 为多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是多任务的一种特别的形式,多线程使用了更小的资源开销。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

这里定义和线程相关的另一个术语 - 进程:也就是一个应用程序。一个进程包括由操作系统分配的内存空间,包含一个或多个线程(至少要有一个线程,叫做主线程)。一个线程不能独立的存在,它必须是进程的一部分。一个进程(程序)会一直运行,直到所有的非守护线程都结束运行后才能结束。

Java的线程是通过java.lang.Thread类来实现的。JVM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。


1、线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。在Java当中,线程通常都有五种状态:创建、就绪、运行、阻塞和死亡。

下图显示了一个线程完整的生命周期。

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。


2、线程的优先级

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

Java 线程的优先级是一个整数,其取值范围是 1-10(Thread.MIN_PRIORITY - Thread.MAX_PRIORITY )。

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

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


3、线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

二、多线程的使用

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

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

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


1、线程创建方法

 线程的创建有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。

多线程原理:相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start()就是进入随时可以开始玩的状态!当CPU分配一个时间片段给你就是轮到你了,你就开始玩run(),当CPU的分配给你的时间片使用完毕,你(线程)就继续排队,等待下一次的run()

调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可运行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行。

1、start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

2、run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

注意:多线程就是分时利用CPU,在宏观上就是让所有线程一起执行 ,也叫并发

  • 采用实现 Runnable接口的方式创建多线程时,线程类只是实现了 Runnable 接口,还可以继承其他类。

  • 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

  • 1、写一个类继承自Thread类,重写run方法。用start方法启动线程:
/**
 * 文件名:MyFirstThreadTest.java
 * 功能描述:继承Thread类,重写run()方法实现多线程功能的测试Demo
 */
class MyThread extends Thread {
    public MyThread() {
        super();
    }

    public MyThread(String threadName) {
        super(threadName);
    }

    @Override
    public void run() {
        super.run();
        for(int i = 5;i > 0; i--) {
            try {
                Thread.sleep(100); //当前线程休眠100ms
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + (i));//输出线程名
        }
    }
}

public class MyThreadTest {
    public static void main(String[] args) {
       Thread t1 = new MyThread();
       Thread t2 = new MyThread();
        t1.start();
        t2.start();
        try {
            for (int i = 1; i <= 5; i++) {
                Thread.sleep(100); //当前线程(即main的主线程)休眠100ms
                System.out.println(Thread.currentThread().getName()+"-->"+(i));
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

 


  • 2、写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动:
/**
 * 文件名:RunnableTest.java
 * 功能描述:使用Runnable接口实现Thread功能的测试Demo
 */
class RunThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.currentThread().sleep(100);//Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        Runnable rt1 = new RunThread();
        Runnable rt2 = new RunThread();

        Thread t1 = new Thread(rt1,"线程1");
        Thread t2 = new Thread(rt2,"线程2");

        t1.start();
        t2.start();
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

运行结果:

  


2、线程的优先级

setPriority()和join()方法的使用举例:

/**
 * 文件名:MyPriorityTest.java
 * 功能描述:多线程优先级功能的测试Demo
 */
class MyThread extends Thread {
    public MyThread() {
        super();
    }

    public MyThread(String threadName) {
        super(threadName);
    }

    @Override
    public void run() {
        super.run();
        for (int i = 5; i > 0; i--) {
            try {
                Thread.sleep(100); //当前线程休眠100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + (i));//输出线程名
        }
    }
}

public class MyPriorityTest {
    public static void main(String[] args) {
        Thread t1 = new MyThread("t1");
        try {
            t1.start();
            //可以比较一下有无t1.join();语句的执行效果
            t1.join(); //等待本线程执行完毕后,才会去执行其他线程,相当提升了t1对象的优先级为最高级(10)

            for (int i = 1; i <= 5; i++) {
                Thread.sleep(100); //当前线程(即main的主线程)休眠100ms
                System.out.println(Thread.currentThread().getName() + "-->" + (i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

有无t1.join();语句的执行结果比较: 

  


3、线程的同步机制

1)为什么要使用synchronized

在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

2)实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

3)synchronized的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

4)synchronized的作用

Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码


5、synchronized用法举例:

/**
 * 文件名:MySynchronizedTest.java
 * 功能描述:线程同步关键字synchronized的用法测试Demo
 */
public class MySynchronizedTest {
    private int shareInt = 0;
    class AddThread extends Thread {
        @Override
        public void run() {
            super.run();
            synchronized (MySynchronizedTest.this) { //对以下代码块加锁,代码块执行完毕,释放该锁
                System.out.println("当前线程:"+Thread.currentThread().getName());//输出线程名
                try {
                    System.out.println("自增之前,shareInt="+shareInt);
                    Thread.sleep(100); //当前线程休眠100ms
                    shareInt ++;
                    System.out.println("自增之后,shareInt="+shareInt);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class SubThread extends Thread {
        @Override
        public void run() {
            super.run();
            synchronized (MySynchronizedTest.this) { //对以下代码块加锁,代码块执行完毕,释放该锁
                System.out.println("当前线程:"+Thread.currentThread().getName());//输出线程名
                try {
                    System.out.println("自减之前,shareInt="+shareInt);
                    Thread.sleep(100); //当前线程休眠100ms
                    shareInt --;
                    System.out.println("自减之后,shareInt="+shareInt);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public MySynchronizedTest() {
        Thread t1 = new AddThread();
        Thread t2 = new SubThread();
        t1.start();
        t2.start();
/*
            for (int i = 1; i <= 5; i++) {
                Thread.sleep(100); //当前线程(即main的主线程)休眠100ms
                System.out.println(Thread.currentThread().getName() + "-->" + (i));
            }
*/
    }

    public static void main(String[] args) throws InterruptedException {
        MySynchronizedTest test = new MySynchronizedTest();
    }
}

 使用synchronized进行加锁前后执行结果对比:

  


三、Java多线程售票小实例

1、错误示范

/**
 * 文件名:SellThreadTest.java
 * 功能描述:多线程售票测试Demo
 */
class SellThread implements Runnable {
    private int i = 5; //票的数量

    public void run() {
        while (true) {
            if (i > 0) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->sell " + i--);
            } else {
                break;
            }
        }
    }
}

/**
 * 文件名:Test.java
 * 功能描述:
 */
public class SellThreadTest {
    public static void main(String[] args) {
        SellThread sell = new SellThread();
        Thread sell1 = new Thread(sell, "sellman1");
        Thread sell2 = new Thread(sell, "sellman2");
        Thread sell3 = new Thread(sell, "sellman3");
        sell1.start();
        sell2.start();
        sell3.start();
    }
}

没有同步控制变量,所以最后出现了一票多卖和-1张票的情况:

 

2、正确示范

class SellThread implements Runnable {
    private int i = 5;
    String key = " ";

    public void run() {
        while(true) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            synchronized (key) {
                if (i > 0) {
                    System.out.println(Thread.currentThread().getName() + "-->sell " + i--);
                } else {
                    break;
                }
            }
        }
    }
}

public class SellThreadTest {

    public static void main(String[] args) {
        SellThread sell = new SellThread();
        Thread sell1 = new Thread(sell, "sellman1");
        Thread sell2 = new Thread(sell, "sellman2");
        Thread sell3 = new Thread(sell, "sellman3");
        sell1.start();
        sell2.start();
        sell3.start();
    }
}

执行结果如下:

补充:如果用synchronized关键字修饰方法,则只能有一个线程获取访问这段方法的权限。也就是说,一个线程把票买完了才结束。

class SellThread implements Runnable {
    private int i = 20;
    String key = "";

    public synchronized void run() {
        while(true) {
            if(i > 0) {
                try {
                    Thread.sleep(100);
                } catch(Exception e) {

                }
                System.out.println(Thread.currentThread().getName() + "-->sell " + i--);
            }else {
                break;
            }
        }
    }
}

public class SellThreadTest {

    public static void main(String[] args) {
        SellThread sell = new SellThread();
        Thread sell1 = new Thread(sell, "sellman1");
        Thread sell2 = new Thread(sell, "sellman2");
        Thread sell3 = new Thread(sell, "sellman3");
        sell1.start();
        sell2.start();
        sell3.start();
    }
}

执行结果:一个人把所有的票都卖完了!

  

3、总结:

1、synchronized()括号中需要的是一个对象,所以这里使用了String类的一个对象key,可以用this来代替key。

2、一个线程拿到synchronized括号中的对象以后,其他线程也是可以执行的,但是当它们需要用key的时候,发现key已经被别人拿走了,只能等着key被释放了。就像上厕所,坑被别人占了,只能等着了,但是等着的时候我还可以运行,比如玩玩手机什么的。

3、synchronized method(){} 可以防止多个线程同时访问这个对象的synchronized方法;如果一个对象A,有多个synchronized方法,只要一个线程访问了其中的一个,那么其他线程不能同时访问A的任何一个synchronized方法。但是可以访问对象B的synchronized方法。

4、synchronized static method(){} 可以防止多个线程同时访问这个类的synchronized方法;对类的所有对象均起作用,无论对象A、对象B、对象C的。
 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值