zoukankan      html  css  js  c++  java
  • (6)简单说说java中的线程

    先甩出来两种创建线程的方法:

     1 private static int count = 100;
     2 
     3 public static void main(String[] args) {
     4     // 用继承Thread类的方式启动一个线程
     5     new Thread() {
     6         public void run() {
     7             synchronized (StartThreadTest.class) {
     8                 while (count > 0) {
     9                     count--;
    10                     System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
    11                 }
    12             }
    13         }
    14     }.start();
    15 
    16     // 用实现Runnable接口的方式启动一个线程
    17     new Thread(new Runnable() {
    18         public void run() {
    19             synchronized (StartThreadTest.class) {
    20                 while (count > 0) {
    21                     count--;
    22                     System.out.println(Thread.currentThread() + "卖了一张票,还剩" + count);
    23                 }
    24             }
    25         }
    26     }).start();
    27 }

     

     不只是线程,在这个javase标签下的所有的笔记都是一些核心的点,甚至有些比较小的内容这里略过,就没有介绍。

    线程

    线程:在一个进程中负责了代码的执行,就是进程中的一个执行路径

    多线程:在一个进程中有多个线程在同时执行不同的任务。

    一个java应用程序至少有几个线程?至少有2个线程,一个是主线程负责了main方法的执行,一个是gc()垃圾回收器的执行,他们是互不干扰的。

    多线程的特点:

    1、解决了一个进程同时执行多个进程的问题

    2、不能提高效率,只是提高了cpu资源的利用率。

    3、引发了线程的安全问题

    4、会出现死锁现象

    如何创建多线程:

    创建线程方式一:

    1自定义一个类继承Thread类。

    2、重写Thread类的run()方法,run()方法中是该线程的任务代码。自定义线程的任务代码写到run()方法中。Jvm创建的主线程的代码,就是main方法中的所有代码。

    3、创建自定义线程。并且调用start()方法开启线程。一旦一个线程开启(start)就会执行run()方法,直接调用run()方法,只相当于调用了一个普通的方法。

    方式二:

      方式二往后面翻

    线程的生命周期  

      其实就是介绍线程的创建、可运行状态、运行状态、阻塞状态、死亡状态,五中状态之间的相互切换。

    常见线程的方法

    Thread(String name)     初始化线程的名字

     getName()             返回线程的名字

     setName(String name)    设置线程对象名

     sleep()                 线程睡眠指定的毫秒数。Static型的,谁执行sleep这句代码,谁睡着,可以t.sleep()调用,也可以Thread.sleep()

     getPriority()             返回当前线程对象的优先级   默认线程的优先级是5

     setPriority(int newPriority) 设置线程的优先级    虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。

     currentThread()      返回CPU正在执行的线程的对象

    注意了在从写run方法的时候使用try-catch捕获处理而不使用throws处理,不然会出错,引原来的父类Threadrun方法就没有throws错误。

    创建线程方式二:使用Runnable创建线程

    1、定义实现Runnable接口的类class myrun inplements Runnable...

    2、重写Runnable接口中的run方法,就是将线程运行的代码放入run

    3、通过通过Thread类建立线程对象 Thread t = new (Runnable run);

    4、Runnable接口的子类对象作为实际参数传递给Thread类的构造方法。

    5、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。

    是这样的,Runnable接口只有一个方法就是run(),别的什么都没有了,包括start()方法,所以要启动其run方法必须使用Thread相关的方法,于是就把,Runnable的子类对象传递给Thread构造出来一个线程然后再启动。这就好比说,Thread是一个工人,其工作是Runnable分配的。

    注意事项:

    1、Runnable实现类的对象是线程对象吗?

    Runnable实现类的对象并不是一个线程的对象,只不过是实现了Runnable接口的一个对象而已罢了。只有Thread或者是Thread的子类才是线程的对象。

    2、为什么把Runnable实现类的对象作为实参传递给Thread的对象,其作用是什么?

    其作用就是把Runnable实现类的run方法作为线程的任务去执行的。

    比较推荐使用第二种Runnable的方法创建线程,这种方法创建线程。其中最大的一个优势是,可以实现所继承呀。

    线程的同步机制 

    两种方式,其实同步机制有很多内容,比如还有Lock锁和reetrantLock锁以及条件对象,具体可以参看javacore。这里只讲了synchronized的两种形式

    同步机制方式一:同步代码块

    synchronized(锁对象)

    {

    需要同步的代码块...

    }

    同步代码块需要注意的事项:

    1.任意个一个对象都可以作为锁对象

    2.在同步代码块中调用sleep函数,并不会释放锁对象。

    3.只有真正存在线程安全问题才需要使用同步代码块,不然的话降低代码执行的效率,不然每次都要判断是否加锁。

    4.多线程操作的多对象必须是唯一共享的否则是无效的。所以这个锁对象需要定义为 static型的,比如static Object o = new Object();注意了最简单的加锁方式:synchronized(”){}这种形式你也能所得住,在常量池中只有一个,所有能够达到共享锁的作用。如果是 synchronized(new String(“”)){}这样的就不行了,new String(”)得到的不是字符串常量中的不变字符,每次都会在堆内存中创建一个

    同步机制方式二;同步函数

    同步函数的注意事项:

    1、如果是一个非静态的同步函数的锁 对象是this,如果是静态的同步函数锁 对象是当前函数所属的类的字节码文件(Class对象----专门用来描述编译之后的字节码文件,说明为什么会想着有ClassClass对象里面维护的是一个类的信息,不如类的方法有哪些访问属性等等,直接查看对应的Class对象即可)。这里具体分析一下,非静态函数每个线程进来,线程自身的this对象就是当前的锁,是锁不住共享资源的。这个时候把我们要同步的函数定义成static形式的,这样的锁,使用的是这个类对应的class对象

    修饰符 synchronized 返回值类型 函数名(参数列表...

    {

    }

    综合比较以上两种同步方式我们推荐使用第一种同步代码块,而不是用第二种同步代码函数,其原因如下:

    1、同步代码块的对象我们可以自由的指定,方便控制,同步函数的锁对象是固定的(就两种,上面说明的有),不能由我们来制定,我们做多自己来选择。

    2、同步代码块可以很方便的来控制需要被同步的代码范围,同步函数必须是整个函数的所有代码都同步了。但往往一个函数中并不是多有的代码都需要同步,这样就会影响整体的效率。

    死锁现象

    代码同步机制实现了共享数据的安全性,但是带来了死锁问题。死锁问题中的经典问题是“哲学家就餐问题”。

    死锁想象出现根本原因:

    1、存在了两个或两个以上的线程

    2、存在两个两个以上的共享资源。

    从技术层面来说没有解决的方案,只能尽可能的避免,也就是尽量避免上面的两种情况

     1 public class DeadLock {
     2     public static void main(String[] args) {
     3         new Thread(new Runnable() { // 创建线程, 代表中国人
     4                     public void run() {
     5                         synchronized ("刀叉") { // 中国人拿到了刀叉
     6                             System.out.println(Thread.currentThread().getName()
     7                                     + ": 你不给我筷子, 我就不给你刀叉");
     8                             try {
     9                                 Thread.sleep(10);
    10                             } catch (InterruptedException e) {
    11                                 e.printStackTrace();
    12                             }
    13                             synchronized ("筷子") {
    14                                 System.out.println(Thread.currentThread()
    15                                         .getName() + ": 给你刀叉");
    16                             }
    17                         }
    18                     }
    19                 }, "中国人").start();  
    20         new Thread(new Runnable() { // 美国人
    21                     public void run() {
    22                         synchronized ("筷子") { // 美国人拿到了筷子
    23                             System.out.println(Thread.currentThread().getName()
    24                                     + ": 你先给我刀叉, 我再给你筷子");
    25                             try {
    26                                 Thread.sleep(10);
    27                             } catch (InterruptedException e) {
    28                                 e.printStackTrace();
    29                             }
    30                             synchronized ("刀叉") {
    31                                 System.out.println(Thread.currentThread()
    32                                         .getName() + ": 好吧, 把筷子给你.");
    33                             }
    34                         }
    35                     }
    36                 }, "美国人").start();
    37     }
    38 }
    View Code

    线程通信

    线程通信:一个线程完成了自己的任务的时候,要通知另外一个线程去完成另外一个任务。

    最最经典的例子就是“生产者与消费者”的例子。我觉着这个例子适合多次看,特别的熟悉。

    Wait():等待 如果线程执行了wait方法那么该线就会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒

    Notify():唤醒 唤醒线程池中等待的线程之一方法

    notifyAll()唤醒 唤醒等待线程池中所有等待的线程

    Wait()notify()方法的注意事项

    1、wait方法和notify方法属于Object类的方法

    2、Wait方法和notify方法必须在同步代码块或者是同步函数中才能使用

    3、Wait()方法和notify()方法必须要有由调用,否则也会调用的

    Wait()方法:一个线程如果执行了wait方法,那么该线程就会进入一个以锁对象为标识符的线程池中。一旦调用wait方法会自动的释放锁。

    Notify():如果一个线程执行notify方法,那么就唤醒以锁对象为标识符的线程池中等待线程中的其中一个。

    为什么把wait方法和notify方法设计到Object上面?因为只有锁对象才调用这两个方法,而任意的对象都可以作为锁对象

    为什么在同步代码块或者是同步函数中调用wait方法和notify方法?因为只有同步代码块才会使用锁对象,而只有锁对象才会调用waitnotify方法

    消费者生产者的例子:

     1 public class Demo10 {
     2     public static void main(String[] args) {
     3         Person p = new Person();
     4         Producer pro = new Producer(p);
     5         Consumer con = new Consumer(p);
     6         Thread t1 = new Thread(pro, "生产者");
     7         Thread t2 = new Thread(con, "消费者");
     8         t1.start();
     9         t2.start();
    10     }
    11 }
    12 
    13 // 使用Person作为数据存储空间
    14 class Person {
    15     String name;
    16     String gender;
    17     
    18 
    19     public synchronized void set(String name, String gender) {
    20         this.name = name;
    21         this.gender = gender;
    22     }
    23 
    24     public synchronized void read() {
    25         System.out.println("name:" + this.name + "----gender:" + this.gender);
    26     }
    27 
    28 }
    29 
    30 // 生产者
    31 class Producer implements Runnable {
    32     Person p;
    33 
    34     public Producer() {
    35 
    36     }
    37 
    38     public Producer(Person p) {
    39         this.p = p;
    40     }
    41 
    42     @Override
    43     public void run() {
    44         int i = 0;
    45         while (true) {
    46 
    47             if (i % 2 == 0) {
    48                 p.set("jack", "man");
    49             } else {
    50                 p.set("小丽", "女");
    51             }
    52             i++;
    53 
    54         }
    55 
    56     }
    57 
    58 }
    59 
    60 // 消费者
    61 class Consumer implements Runnable {
    62     Person p;
    63 
    64     public Consumer() {
    65 
    66     }
    67 
    68     public Consumer(Person p) {
    69         this.p = p;
    70     }
    71 
    72     @Override
    73     public void run() {
    74 
    75         while (true) {
    76             p.read();
    77 
    78         }
    79     }
    80 
    81 }
    View Code

    wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。

    notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。

    notifyAll:唤醒持有同一监视器中调用wait的所有的线程。

    如何解决生产者和消费者的问题?

    可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true

    ,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。

    代码实现:

    package cn.itcast.gz.runnable;
    
    public class Demo10 {
        public static void main(String[] args) {
            Person p = new Person();
            Producer pro = new Producer(p);
            Consumer con = new Consumer(p);
            Thread t1 = new Thread(pro, "生产者");
            Thread t2 = new Thread(con, "消费者");
            t1.start();
            t2.start();
        }
    }
    
    // 使用Person作为数据存储空间
    class Person {
        String name;
        String gender;
        boolean flag = false;
    
        public synchronized void set(String name, String gender) {
            if (flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            }
            this.name = name;
            this.gender = gender;
            flag = true;
            notify();
        }
    
        public synchronized void read() {
            if (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            }
            System.out.println("name:" + this.name + "----gender:" + this.gender);
            flag = false;
            notify();
        }
    
    }
    
    // 生产者
    class Producer implements Runnable {
        Person p;
    
        public Producer() {
    
        }
    
        public Producer(Person p) {
            this.p = p;
        }
    
        @Override
        public void run() {
            int i = 0;
            while (true) {
    
                if (i % 2 == 0) {
                    p.set("jack", "man");
                } else {
                    p.set("小丽", "女");
                }
                i++;
    
            }
    
        }
    
    }
    
    // 消费者
    class Consumer implements Runnable {
        Person p;
    
        public Consumer() {
    
        }
    
        public Consumer(Person p) {
            this.p = p;
        }
    
        @Override
        public void run() {
    
            while (true) {
                p.read();
    
            }
        }
    
    }
    View Code

    线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,waitnotify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。

    为什么这些方法定义在Object类中

    因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中

    wait() sleep()有什么区别?

    wait():释放资源,释放锁。是Object的方法

    sleep():释放资源,不释放锁。是Thread的方法

    定义了notify为什么还要定义notifyAll,因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。

    线程的停止

    Stop()方法已经不推荐使用了

    Interrupt()方法不用在同步代码快中使用 该方法强制清除线程的等待状态 该方法简单粗暴,强制清除等待状态,会抛出异常。Noitify()方法比较温和,能够唤醒阻塞队列里的某个线程但无法指定具体的某个线程。

    线程的停止方法:

    1、一般通过一个变量去控制,线程的主要内容是run()方法,这个方法中我们一般写的是一个while(true){}的循环,所以我们一般定义一个flag 用一个标志变量来控制线程的结束,这是比较好的一种用法。

    2、如果要停止一个处于“等待”状态下的线程,我们需要变量配合或notify方法或者interrupt方法。

     线程生命周期

    任何事物都是生命周期,线程也是,

    1. 正常终止  当线程的run()执行完毕,线程死亡。

    2. 使用标记停止线程

    注意:Stop方法已过时,就不能再使用这个方法。

    如何使用标记停止线程停止线程。

    开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,线程就结束。

     1 class StopThread implements Runnable {
     2     public boolean tag = true;
     3     @Override
     4     public void run() {
     5         int i = 0;
     6 
     7         while (tag) {
     8             i++;
     9             System.out.println(Thread.currentThread().getName() + "i:" + i);
    10         }
    11     }
    12 }
    13 public class Demo8 {
    14     public static void main(String[] args) {
    15         StopThread st = new StopThread();
    16         Thread th = new Thread(st, "线程1");
    17         th.start();
    18         for (int i = 0; i < 100; i++) {
    19             if (i == 50) {
    20                 System.out.println("main i:" + i);
    21                 st.tag = false;
    22             }
    23         }
    24     }
    25 }
    View Code

    后台线程

    后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。

     实现:

          setDaemon(boolean on)

     特点:

    当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。

    必须在启动线程之前(调用start方法之前)调用setDaemontrue)方法,才可以把该线程设置为后台线程。

    一旦main()执行完毕,那么程序就会终止,JVM也就退出了。

    可以使用isDaemon() 测试该线程是否为后台线程(守护线程)。

    该案例:开启了一个qq检测升级的后台线程,通过while真循环进行不停检测,当计数器变为100的时候,表示检测完毕,提示是否更新,线程同时结束。

    为了验证,当非后台线程结束时,后台线程是否终止,故意让该后台线程睡眠一会。发现只要main线程执行完毕,后台线程也就随之消亡了。

     1 class QQUpdate implements Runnable {
     2     int i = 0;
     3 
     4     @Override
     5     public void run() {
     6         while (true) {
     7 
     8             System.out.println(Thread.currentThread().getName() + " 检测是否有可用更新");
     9             i++;
    10             try {
    11                 Thread.sleep(10);
    12             } catch (InterruptedException e) {
    13 
    14                 e.printStackTrace();
    15             }
    16             if (i == 100) {
    17                 System.out.println("有可用更新,是否升级?");
    18                 break;
    19             }
    20         }
    21     }
    22 }
    23 public class Demo9 {
    24     public static void main(String[] args) {
    25         QQUpdate qq = new QQUpdate();
    26         Thread th = new Thread(qq, "qqupdate");
    27         th.setDaemon(true);
    28         th.start();
    29         System.out.println(th.isDaemon());
    30         System.out.println("hello world");
    31     }
    32 }
    View Code

    Threadjoin方法

    A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行

    本案例,启动了一个JoinThread线程,main(主线程)进行for循环,当计数器为50时,让JoinThread,通过join方法,加入到主线程中,发现只有JoinThread线程执行完,主线程才会执行完毕.

    可以刻意让JoinThread线程sleep,如果JoinThread没有调用join方法,那么肯定是主线程执行完毕,但是由于JoinThread线程加入到了main线程,必须等JoinThread执行完毕主线程才能继续执行。

     1 class JoinThread implements Runnable {
     2 
     3     @Override
     4     public void run() {
     5         int i = 0;
     6         while (i < 300) {
     7             try {
     8                 Thread.sleep(1000);
     9             } catch (InterruptedException e) {
    10                 e.printStackTrace();
    11             }
    12             System.out.println(Thread.currentThread().getName() + " i:" + i);
    13             i++;
    14         }
    15     }
    16 }
    17 
    18 public class Demo10 {
    19     public static void main(String[] args) throws InterruptedException {
    20         JoinThread jt = new JoinThread();
    21         Thread th = new Thread(jt, "one");
    22         th.start();
    23         int i = 0;
    24         while (i < 200) {
    25             if (i == 100) {
    26                 th.join();
    27             }
    28             System.err.println(Thread.currentThread().getName() + " i:" + i);
    29             i++;
    30 
    31         }
    32     }
    33 }
    View Code
  • 相关阅读:
    【软件测试】软件缺陷粗浅认识及白盒测试举例
    【软件测试】等价类划分
    【软件测试】对本门课程粗浅理解
    阿里云服务器本地ping超时,远程可以正常ping通
    不忘初心
    开源框架、控件、组件、插件记录
    Flex中窗口可随意拖拽功能实现
    初探数据类型相关问题
    [TSCTF-J 2021] 解题报告
    指针
  • 原文地址:https://www.cnblogs.com/OliverZhang/p/6014012.html
Copyright © 2011-2022 走看看