zoukankan      html  css  js  c++  java
  • JAVA基础——多线程

    一、什么是线程

    • 进程:操作系统中每个独立执行的程序都可称为一个进程。
    • 线程:一个程序中能够同时运行的执行单元。

    例:如果将QQ看作一个进程,则其中同时打开的聊天窗口则可看为一个个线程。

    注:多线程程序运行时,每个线程之间是独立的,可以并发执行。但并不是同时执行,CPU同一时刻只能执行一个线程。

    二、创建线程的两种方式

    1、继承Thread类

    Thread类位于java.lang包下,通过覆写Thread类的run()方法实现多线程。

    示例:

    public class Example_1 {
        public static void main(String[] args) {
            MyThread one = new MyThread();
            one.start();
            while(true){
                System.out.println("main()方法在运行!");
            }
        }
    }
    
    class MyThread extends Thread{
        //重写run()方法
        public void run(){
            while(true){
                System.out.println("MyThread类的run()方法在运行!");
            }
        }
    }

    注:有局限性,Java中一个类只能继承一个父类。

    2、实现Runnable接口

    其中只有一个run方法,通过构造函数Thread(Runnable target)创建线程对象。避免了局限性。

    示例:

    public class Example_2 {
        public static void main(String[] args) {
            MyThread_2 two = new MyThread_2();    //创建MyThread_2实例对象
            Thread thread = new Thread(two);      //创建线程对象
            thread.start();                       //开启线程
            while(true){
                System.out.println("main()方法在运行!");
            }
        }
    }class MyThread_2 implements Runnable{
        public void run(){
            while(true){
                System.out.println("MyThread_2类的run()方法在运行");
            }
        }
    }

    3、两种方式的对

    实现Runnable接口相对于继承Thread类来说有以下好处:

      ① 适合多个相同程序代码的线程取处理同一个资源的情况,把线程同程序代码,数据有效的分离,体现了面向对象的特点。
      ② 可以避免由于java的单继承带来的局限性。

    示例:

    public class Example_2 {
        public static void main(String[] args) {
            new TicketWindow().start();        //创建一个线程对象TicketWindow并开启
            new TicketWindow().start();        //创建一个线程对象TicketWindow并开启
            new TicketWindow().start();        //创建一个线程对象TicketWindow并开启
            new TicketWindow().start();        //创建一个线程对象TicketWindow并开启
            
            //TicketWindow_2 tw = new TicketWindow_2();    //创建TicketWindow_2实例对象
            //new Thread(tw,"窗口1").start();    //创建线程并命名,开启线程
            //new Thread(tw,"窗口2").start();    //创建线程并命名,开启线程
            //new Thread(tw,"窗口3").start();    //创建线程并命名,开启线程
            //new Thread(tw,"窗口4").start();    //创建线程并命名,开启线程
        }
    }
    
    class TicketWindow extends Thread{
        private int tickets = 100;
        public void run(){
            while(true){                    //通过死循环打印语句
                if(tickets > 0){
                    Thread th = Thread.currentThread();        //获取当前线程
                    String th_name = th.getName();             //获取当前线程的名字
                    System.out.println(th_name + "正在发售第" + tickets-- + "张票");
                }
            }
            
        }
    }
    
    class TicketWindow_2 implements Runnable{
        private int tickets = 100;
        public void run(){
            while(true){                    //通过死循环打印语句
                if(tickets > 0){
                    Thread th = Thread.currentThread();        //获取当前线程
                    String th_name = th.getName();             //获取当前线程的名字
                    System.out.println(th_name + "正在发售第" + tickets-- + "张票");
                }
            }
        }
    }

    4、后台线程

    在java程序中,只要还有一个前台程序在运行,这个线程就不会结束,如果一个程序中只有后台线程运行,这个进程就会结束。

      ①新创建的线程默认都是前台线程
      ②如果某个线程对象在启动之前调用了setDaemon(true)语句,这个线程就变成一个后台线程。

    注意:要将某个线程设置为后台线程,必须在该线程启动之前,也就是说setDaemon()方法必须在start()方法之前调用。否则会引发
    IllegalThreadStateException异常。

    代码:

    class DamonThread implements Runnable{
        public void run(){
            while(true){
                System.out.println(Thread.currentThread().getName() + "---is running.");
            }
        }
    }
    
    public class Example_3 {
        public static void main(String[] args) {
            System.out.println("main线程是后台线程么?" + Thread.currentThread().isDaemon());
            DamonThread one = new DamonThread();               //创建一个DamonThread实例对象
            Thread thread = new Thread(one,"后台线程");         //创建线程thread共享one资源
            System.out.println("thread线程是后台线程么?" + Thread.currentThread().isDaemon());
            thread.setDaemon(true);                            //将线程thread设为后台线程
            thread.start();                                    //开启thread线程
            for(int i = 0; i < 10; i++){
                System.out.println(i);
            }
        }
    }

    三、线程的生命周期

    从Thread对象创建完毕开始,当run()方法中代码正常执行完毕或者抛出一个未捕获的异常(Exception)或者错误(Error)时,线程的生命周期便会结束。

     ①、新建状态(New)

    创建一个线程后,该线程对象就处于就绪状态。仅仅由Java虚拟机为其分配了内存,不能运行。

     ②、就绪状态(Runnable)

    当线程对象调用了start()方法后,该进程就进入就绪状态。处于就绪状态的线程位于可运行池中,此时它只是具备了运行的条件。

     ③、运行状态(Running)

    如果处于就绪状态的线程获得了CPU的使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态。

    注:只有处于就绪状态的线程才可能转换到运行状态。

     ④、阻塞状态(Blocked)

    一个正在执行的线程在某些特殊情况下,如执行耗时的输入/输出操作时,会放弃CPU使用权,进入阻塞状态。

    注:线程进入阻塞状态后就不能进入排队队列。只有当引起阻塞原因被消除后,线程才可以转入就绪状态。

    线程由运行状态转化成阻塞状态的原因,及如何从阻塞状态转换为就绪状态:

    • 当线程需要从其他线程获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程进入阻塞状态。如果想从阻塞状态进入就绪状态必须得获取到其他线程所持有的锁。
    • 当线程调用了一个阻塞式的IO方法时,该线程进入阻塞状态,如果想进入就绪状态就必须等到这个阻塞的IO方法返回。
    • 当线程调用了某个对象的wait()方法时,该线程进入阻塞状态,可使用notyfy()方法唤醒该线程使其进入就绪状态。
    • 当线程调用了Thread的sleep(long millis)方法时,线程进入阻塞状态,需等线程睡眠时间到了以后,线程就会自动进入就绪状态。
    • 当在一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,需要等到新加入的线程运行结束后才会结束阻塞状态进入就绪状态。

     ⑤、死亡状态(Terminated)

    线程的run()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入死亡状态。

    注:一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其他其他状态。

    四、线程的调度

    即Java虚拟机按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制称为线程的调度。

    1、线程的优先级

    线程的优先级用1-10之间的整数来表示,数字越大优先级越高。可以用Thread类中提供的静态常量来表示线程的优先级。

    Thread类的优先级常量:

      static int MAX_PRIORITY    表示线程的最高优先级,相当于值10

      static int MIN_PRIORITY    表示线程的最低优先级,相当于1

      static int NORM_PRIORITY     表示线程的普通优先级,相当于5  

    注:main线程具有普通优先级。可以通过Thread类的setPriority(int newPriority)方法对其进行设置。

    示例:

    class MaxPriority implements Runnable{
        public void run(){
            for(int i=0; i<=10; i++){
                System.out.println(Thread.currentThread().getName() + "正在输出" + i);
            }
        }
    }
    
    class MinPriority implements Runnable{
        public void run(){
            for(int i=0; i<=10; i++){
                System.out.println(Thread.currentThread().getName() + "正在输出" + i);
            }
        }
    }
    
    public class Example_4 {
        public static void main(String[] args) {
            //创建两个线程
            Thread minPriority = new Thread(new MinPriority(),"优先级较低的线程");
            Thread maxPriority = new Thread(new MaxPriority(),"优先级较高的线程");
            minPriority.setPriority(Thread.MIN_PRIORITY);
            maxPriority.setPriority(10);
            //开启两个线程
            maxPriority.start();
            minPriority.start();
        }
    }

    2、线程休眠

    可以通过静态方法sleep(long millis)将CPU让给别的线程,该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。

    注:sleep(long millis)方法声明抛出InterruptedException异常,因此调用该方法时应该捕捉异常,或者声明抛出该异常。

    示例:

    class SleepThread implements Runnable{
        public void run(){
            for(int i=0; i<=10; i++){
                if(i == 3){
                    try{
                        Thread.sleep(200);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }    
                System.out.println("线程一正在输出:" + i);
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    public class Example_5 {
        public static void main(String[] args) throws InterruptedException {
            //创建一个线程
            new Thread(new SleepThread()).start();
            for(int i=1; i<=10; i++){
                if(i == 5){
                    Thread.sleep(2000);
                }
                System.out.println("主线程正在输出" + i);
                Thread.sleep(500);
            }
        }
    }

    注:sleep()是静态方法,只能控制当前正在运行的线程休眠,而不能控制其他线程休眠。

    3、线程让步

    线程让步可以通过yield()方法来实现,该方法和sleep()方法有点相似,区别是yield()方法不会阻塞该线程,它只是将线程转换成就绪状态让系统重新调度一次。

    注:当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。

    示例:

    class YieldThread extends Thread{
        public YieldThread(String name){
            super(name);                //调用父类构造方法
        }
        public void run(){
            for(int i=0; i<=500; i++){
                System.out.println(Thread.currentThread().getName() + "---" + i);
                if(i == 3){
                    System.out.println("线程让步:");
                    Thread.yield();        //线程运行到此,做出让步    
                }    
            }
        }
    }
    
    public class Example_6 {
        public static void main(String[] args) {
            //创建两个线程
            Thread one = new YieldThread("线程A");
            Thread two = new YieldThread("线程B");
            //开启线程
            one.start();
            two.start();
        }
    }

    4、线程插队

    在Thread类中提供了一个join()方法来实现“插队”,即当在某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完毕后它才会继续运行。

    示例:

    class EmergencyThread implements Runnable{
        public void run(){
            for(int i=0; i<10; i++){
                System.out.println(Thread.currentThread().getName() + "输入:" + i);
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    public class Example_7 {
        public static void main(String[] args) throws Exception {
            //创建线程
            Thread thread = new Thread(new EmergencyThread(),"线程一");
            thread.start();                    //开启线程
            for(int i=0; i<10; i++){
                System.out.println(Thread.currentThread().getName() + "输入:" + i);
                if(i == 2){
                    thread.join();            //调用join()方法
                }
                Thread.sleep(500);
            }
        }
    }

    五、多线程同步

    为了解决多个线程访问统一个资源时引发的一些安全问题。

    解决方法:实现多线程的同步,即限制某个资源在同一时刻只能被一个线程访问。

    1、同步代码块

    同步代码块:同步机制
     当多个线程使用一个共享资源时,可以将处理共享资源的代码放在一个代码块中,使用synchronized关键字来修饰,被称作同步代码块

    语法格式:
     synchronized(lock){
         操作共享资源代码块
     }
     * #lock是一个锁对象是同步代码块的关键
     * #锁对象的类型可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的
     * #锁对象的创建代码不能放到run()方法中。

    示例:

    class SaleThread implements Runnable{
        private int tickets = 10;
        Object lock = new Object();                //定义任意一个对象,作为同步代码块的锁
        public void run(){
            while(true){
                synchronized(lock){                //定义同步代码块
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    if(tickets > 0){
                        System.out.println(Thread.currentThread().getName() + "---卖出的票" + tickets--);
                    }else{
                        break;
                    }
                }
            }
        }
    }
    public class Example_8 {
        public static void main(String[] args) {
            SaleThread one = new SaleThread();        //创建saleThread对象
            //创建并开启四个线程
            new Thread(one,"线程一").start();
            new Thread(one,"线程二").start();
            new Thread(one,"线程三").start();
            new Thread(one,"线程四").start();
        }
    }

    2、同步方法

    方法前面同样可以使用synchronized关键字来修饰,被修饰的方法为同步方法,能实现和同步代码块相同的功能。

    具体格式为:

    synchronized 返回值类型 方法名([参数 1, ......]){}

    被synchronized修饰的方法某一时刻值允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行方法。

    示例:

    class Ticket1 implements Runnable{
        private int tickets = 10;
        public void run(){
            while(true){
                saleTicket();
                if(tickets <= 0){
                    break;
                }
            }
        }
        private synchronized void saleTicket(){
            if(tickets > 0){
                try{
                    Thread.sleep(10);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "---卖出的票" + tickets--);
            }
        }
    }
    public class Example_9 {
        public static void main(String[] args) {
            Ticket1 ticket = new Ticket1();
            //创建并开启四个线程
            new Thread(ticket,"线程一").start();
            new Thread(ticket,"线程二").start();
            new Thread(ticket,"线程三").start();
            new Thread(ticket,"线程四").start();
        }
    }

    注:同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象。

    优点:同步方法被所有线程所共享,方法所在的对象相对于所有线程来说是唯一的,从而保证了锁的唯一性。

    3、死锁问题

    死锁问题:两个线程运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。

    示例:

    public class Example_10 {
        public static void main(String[] args) {
            //创建两个DeadLockThread对象
            DeadLockThread one = new DeadLockThread(true);
            DeadLockThread two = new DeadLockThread(false);
            //创建并开启两个线程
            new Thread(one,"Chinese").start();
            new Thread(two,"American").start();
        }
    }
    
    class DeadLockThread implements Runnable{
        static Object chopsticks = new Object();        //定义Object类型的chopsticks对象
        static Object knifeAndFork = new Object();        //定义Object类型的knifeAndFork对象
        private boolean flag;                            //定义布尔类型变量flag
        
        DeadLockThread(Boolean flag){                
            this.flag = flag;
        }
    
        public void run() {
            if(flag){
                while(true){
                    synchronized(chopsticks){            //chopsticks锁对象上的同步代码块
                        System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
                        synchronized(knifeAndFork){
                            System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
                        }
                    }
                }
            }else{
                while(true){
                    synchronized(knifeAndFork){            //knifeAndFork锁对象上的同步代码块
                        System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
                        synchronized(chopsticks){
                            System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
                        }
                    }
                }
            }
        }
    }
  • 相关阅读:
    fabu dao fenleizhong
    net core 发布到服务器的方式
    暗示net core
    new post
    gg
    asb
    Nodejs 8.0 踩坑经验汇总
    禁止未发布的博文上首页等
    SortedList
    JSON.stringify 语法实例讲解
  • 原文地址:https://www.cnblogs.com/dfif/p/12122366.html
Copyright © 2011-2022 走看看