zoukankan      html  css  js  c++  java
  • JavaSE学习 第十六章 线程

    1.什么是线程

    线程是一个程序内部的顺序控制流。

    线程和进程:

    每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。

    线程: 轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。

    多进程: 在操作系统中能同时运行多个任务(程序)

    多线程:在同一应用程序中有多个顺序流同时执行

    2.线程的概念模型

    1. 虚拟的CPU,由java.lang.Thread类封装和虚拟;

    2. CPU所执行的代码,传递给Thread类对象;

    3. CPU所处理的数据,传递给Thread类对象。

    1

    Java的线程是通过java.lang.Thread类来实现的。

    每个线程都是通过某个特定Thread对象所对应的,方法run( )来完成其操作的,方法run( )称为线程体。

    3.创建线程的两种方式的比较

    一. 使用Runnable接口创建线程:

    可以将CPU(Tread 类)、代码和数据(Runnable接口 run 方法)分开,形成清晰的模型;线程体run()方法所在的类还可以从其他类继承一些有用的属性或方法;

    并有利于保持程序风格的一致性。

    public class TestTread1{
        public static void main(String args[]) {
            Runner1 r = new Runner1();
            Thread t = new Thread(r);
            t.start();
        }
    }
    
    
    class Runner1 implements Runnable {
        public void run() {
            for(int i=0; i<30; i++) {
                System.out.println("No. " + i);
            }
        }
    }

    二. 直接继承Thread类创建线程:

    Thread子类无法再从其他类继承,编写简单,run()方法的当前对象就是线程对象,可直接操纵。

    public class TestTread2{
        public static void main(String args[]){
            Thread t = new Runner3();
            t.start();
        }
    }
    
    
    class Runner3 extends Thread {
        public void run() {
            for(int i=0; i<30; i++) {
                System.out.println("No. " + i);
            }
        }
    }

    4.多线程

    Java 中 引 入 线 程 机 制 的 目 的 在 于 实 现 多 线 程 (Multi-Thread),多线程之间可以共享代码和数据。

    public class TestTread3{
        public static void main(String args[]) {
            Runner2 r = new Runner2();
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
            t1.start();
            t2.start();
        }
    }
    
    
    class Runner2 implements Runnable {
        public void run() {
            for(int i=0; i<10; i++) {
                String s = Thread.currentThread().getName();
                System.out.println(s + ": " + i);
            }
        }
    }

    结果:

    Thread-0: 0
    Thread-0: 1
    Thread-0: 2
    Thread-0: 3
    Thread-0: 4
    Thread-0: 5
    Thread-0: 6
    Thread-0: 7
    Thread-0: 8
    Thread-0: 9
    Thread-1: 0
    Thread-1: 1
    Thread-1: 2
    Thread-1: 3
    Thread-1: 4
    Thread-1: 5
    Thread-1: 6
    Thread-1: 7
    Thread-1: 8
    Thread-1: 9

    2

    5.后台线程

    相关基本概念:

    后台处理(Background Processing):让某些程序的运行让步给其他的优先级高的程序

    后台线程(Background Thread / Daemon Thread):以后台的方式(往往是服务线程)运行的线程,例如定时器线程

    用户线程(User Thread):完成用户指定的任务的线程

    主线程(Main Thread):它就是一种主线程

    子线程(Sub Thread):在线程中创建的线程就是子线程

    Thread类提供的相关方法:

    public final boolean isDaemon():判断某个线程是不是一个后台线程

    public final void setDaemon(Boolean on):设置某个线程为一个后台线程

    举例:

    package v512.chap16;
    public class TestDaemonThread {    
        public static void main(String args[]){
            Thread t1 = new MyRunner(10);
            t1.setName("用户线程t1");
            t1.start();
            
            Thread t2 = new MyRunner(50);
            t2.setDaemon(true);
            t2.setName("后台线程t2");
            t2.start();
            
            for(int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName() + ": " + i);    
            }
            System.out.println("主线程结束!");
        }
    }
    
    class MyRunner extends Thread {
        private int n;
        public MyRunner(int n){
            this.n = n;    
        }
        public void run() {
            for(int i=0; i<n; i++) {    
                    System.out.println(this.getName() + ": " + i);
            }
            System.out.println(this.getName() + "结束!");
        }
    }

    结果:

    用户线程t1: 0
    用户线程t1: 1
    用户线程t1: 2
    用户线程t1: 3
    用户线程t1: 4
    用户线程t1: 5
    用户线程t1: 6
    用户线程t1: 7
    用户线程t1: 8
    用户线程t1: 9
    用户线程t1结束!
    main: 0
    main: 1
    main: 2
    main: 3
    main: 4
    main: 5
    main: 6
    main: 7
    main: 8
    main: 9
    主线程结束!
    后台线程t2: 0
    后台线程t2: 1
    后台线程t2: 2
    后台线程t2: 3
    后台线程t2: 4
    后台线程t2: 5
    后台线程t2: 6
    后台线程t2: 7
    后台线程t2: 8
    后台线程t2: 9
    后台线程t2: 10
    后台线程t2: 11
    后台线程t2: 12
    后台线程t2: 13
    后台线程t2: 14
    后台线程t2: 15
    后台线程t2: 16
    后台线程t2: 17
    后台线程t2: 18
    后台线程t2: 19
    后台线程t2: 20
    后台线程t2: 21
    后台线程t2: 22
    后台线程t2: 23
    后台线程t2: 24
    后台线程t2: 25
    后台线程t2: 26
    后台线程t2: 27
    后台线程t2: 28
    后台线程t2: 29
    后台线程t2: 30
    后台线程t2: 31
    后台线程t2: 32
    后台线程t2: 33
    后台线程t2: 34
    后台线程t2: 35
    后台线程t2: 36
    后台线程t2: 37
    后台线程t2: 38
    后台线程t2: 39
    后台线程t2: 40
    后台线程t2: 41
    后台线程t2: 42
    后台线程t2: 43
    后台线程t2: 44
    后台线程t2: 45
    后台线程t2: 46
    后台线程t2: 47
    后台线程t2: 48
    后台线程t2: 49
    后台线程t2结束!

    6.GUI线程

    GUI程序运行过程中系统会自动创建若干GUI线程

    常见GUI线程:

    AWT-Windows线程:(windows 系统中)后台线程

    AWT-EventQueue-n线程

    AWT-Shutdown线程:负责关闭窗体中的抽象窗口工具

    DestroyJavaVM线程:不是GUI线程,它是当main线程结束之后系统自动创建的,用于等到所有的线程都死掉了之后销毁jvm

    实例:

    package v512.chap16;
    import java.awt.*;
    import java.awt.event.*;
    public class TestGUIThread {    
        public static void main(String args[]) throws Exception{
            Frame f = new Frame();
            Button b = new Button("Press Me!");
            MyMonitor mm = new MyMonitor();
            b.addActionListener(mm);
            f.addWindowListener(mm);    
            f.add(b,"Center");    
            f.setSize(100,60);
            f.setVisible(true);
            MyThreadViewer.view();
        }
    }
    
    class MyMonitor extends WindowAdapter implements ActionListener{
        public void actionPerformed(ActionEvent e){
            MyThreadViewer.view();    
        }
    }
    
    class MyThreadViewer{
        public static void view(){
            Thread current = Thread.currentThread();
            System.out.println("当前线程名称: " + current.getName());
            int total = Thread.activeCount();
            System.out.println("活动线程总数: " + total + "个");
            Thread[] threads = new Thread[total];        
            current.enumerate(threads);
            for(Thread t : threads){
                String role = t.isDaemon()?"后台线程 ":"用户线程 ";
                System.out.println("   -" + role + t.getName());    
            }    
            System.out.println("----------------------------------");
        }    
    }

    运行之后,在点击一下 button

    输出结果:

    当前线程名称: main
    活动线程总数: 4个
       -用户线程 main
       -用户线程 AWT-Shutdown
       -后台线程 AWT-Windows
       -用户线程 AWT-EventQueue-0
    ----------------------------------
    当前线程名称: AWT-EventQueue-0
    活动线程总数: 4个
       -用户线程 AWT-Shutdown
       -后台线程 AWT-Windows
       -用户线程 AWT-EventQueue-0
       -用户线程 DestroyJavaVM
    ----------------------------------

    7.线程的生命周期

    新建状态

    就绪状态:此时不一定马上就开始运行                                                      

    运行状态:运行状态的线程可能会发生问题(赚到了阻塞状态)

    阻塞状态

    终止状态

    3

    8.线程的优先级

    线程的优先级用数字来表示,范围从1到10。主线程的缺省优先级是5,子线程的优先级默认与其父线程相同。

    注意:程序中不能依赖线程的优先级来让线程按照我们的要求来执行!

    Thread类提供的相关方法:

    public final int getPriority();

    public final void setPriority(int newPriority);

    相关静态整型常量:

    Thread.MIN_PRIORITY = 1

    Thread.MAX_PRIORITY = 10

    Thread.NORM_PRIORITY = 5

    用法举例:

    package v512.chap16;
    public class TestPriority {    
        public static void main(String args[]){
            System.out.println("线程名\t优先级");
            Thread current = Thread.currentThread();
            System.out.print(current.getName() + "\t");
            System.out.println(current.getPriority());        
            Thread t1 = new Runner();
            Thread t2 = new Runner();
            Thread t3 = new Runner();
            t1.setName("First");        
            t2.setName("Second");        
            t3.setName("Third");        
            t2.setPriority(Thread.MAX_PRIORITY);
            t3.setPriority(8);
               t1.start();
            t2.start();
               t3.start();
        }
    }
    
    class Runner extends Thread {
        public void run() {
            System.out.print(this.getName() + "\t");
            System.out.println(this.getPriority());        
        }
    }

    输出结果:

    线程名    优先级
    main    5
    First    5
    Second    10
    Third    8

    9.线程的串行化

    在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串型化处理。

    Thread类提供的相关方法:

    public final void join()

    public final void join(long millis)  //等待毫秒数

    public final void join(long millis,int nanos)

    测试 主线程必须要等到子线程运行完成之后才可以运行:

    package v512.chap16;
    public class TestJoin {    
        public static void main(String args[]){
            MyRunner2 r = new MyRunner2();
            Thread t = new Thread(r);
            t.start();
            try{
                t.join();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            for(int i=0;i<20;i++){
                System.out.println("主线程:" + i);
            }
        }
    }
    
    class MyRunner2 implements Runnable {
        public void run() {
            for(int i=0;i<20;i++) {
                System.out.println("SubThread: " + i);
            }
        }
    }

    输出结果:

    SubThread: 0
    SubThread: 1
    SubThread: 2
    SubThread: 3
    SubThread: 4
    SubThread: 5
    SubThread: 6
    SubThread: 7
    SubThread: 8
    SubThread: 9
    SubThread: 10
    SubThread: 11
    SubThread: 12
    SubThread: 13
    SubThread: 14
    SubThread: 15
    SubThread: 16
    SubThread: 17
    SubThread: 18
    SubThread: 19
    主线程:0
    主线程:1
    主线程:2
    主线程:3
    主线程:4
    主线程:5
    主线程:6
    主线程:7
    主线程:8
    主线程:9
    主线程:10
    主线程:11
    主线程:12
    主线程:13
    主线程:14
    主线程:15
    主线程:16
    主线程:17
    主线程:18
    主线程:19

    10.线程休眠

    线程休眠——暂停执行当前运行中的线程,使之进入阻塞状态,待经过指定的“延迟时间”后再醒来并转入到就绪状态。

    Thread类提供的相关方法:

    public static void sleep(long millis)

    public static void sleep(long millis, int nanos)

    用法举例: 数字时钟

    package v512.chap16;
    import java.util.*;
    import javax.swing.*;
    
    public class DigitalClock{
        public static void main(String[] args){
            JFrame jf = new JFrame("Clock");
            JLabel clock = new JLabel("Clock");
            clock.setHorizontalAlignment(JLabel.CENTER);
            jf.add(clock,"Center");
            jf.setSize(140,80);
            jf.setLocation(500,300);
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);
            
            while(true){
                clock.setText(getTime());
                try{
                    Thread.sleep(1000); //this.sleep(1000);    
                }catch(InterruptedException e){
                    e.printStackTrace();    
                }    
            }    
        }
        public static String getTime(){
            Calendar c = new GregorianCalendar();
            String time = c.get(Calendar.YEAR) + "-" +
                          (c.get(Calendar.MONTH) + 1) + "-" +
                          c.get(Calendar.DATE)  + "  " ;
            int h = c.get(Calendar.HOUR_OF_DAY);
            int m = c.get(Calendar.MINUTE);
            int s = c.get(Calendar.SECOND);
            String ph = h<10 ? "0":"";
            String pm = m<10 ? "0":"";
            String ps = s<10 ? "0":"";        
            time += ph + h + ":" + pm + m + ":" + ps + s; 
            return time;
        }    
    }

    显示效果:

    4

    11.线程让步

    线程让步——让运行中的线程主动放弃当前获得的 CPU 处理机会,但不是使该线程阻塞,而是使之转入就绪状态。

    Thread类提供的相关方法:

    public static void yield()

    用法举例:

    package v512.chap16;
    import java.util.Date;
    
    public class TestYield{
        public static void main(String[] args){
            Thread t1 = new MyThread(false);
            Thread t2 = new MyThread(true);
            Thread t3 = new MyThread(false);
            t1.start();
            t2.start();
            t3.start();
        }    
    }
    
    class MyThread extends Thread{
        private boolean flag;
        public MyThread(boolean flag){
            this.flag = flag;
        }
        public void setFlag(boolean flag){
            this.flag = flag;
        }
        public void run(){
            long start = new Date().getTime();
            for(int i=0;i<500;i++){
                if(flag)
                    Thread.yield();
                System.out.print(this.getName() + ": " + i + "\t");
            }
            long end = new Date().getTime();
            System.out.println("\n" + this.getName() + "执行时间: " + (end - start) + "毫秒");
        }
    }

    输出结果太多了,不便贴出,可以查看一下执行时间

    Thread-0执行时间: 67毫秒

    Thread-2执行时间: 84毫秒

    Thread-1执行时间: 148毫秒

    12.线程的挂起和恢复

    线程挂起——暂时停止当前运行中的线程,使之转入阻塞状态,并且不会自动恢复运行。

    线程恢复——使得一个已挂起的线程恢复运行。

    Thread类提供的相关方法:

    public final void suspend()

    public final void resume()

    用法举例:

    package v512.chap16;
    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Date;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JButton;
    
    public class TestSuspend{
        public static void main(String[] args){
            JFrame jf = new JFrame("Timer");
            JButton pause = new JButton("Pause");
            JLabel clock = new JLabel("Timer");
            clock.setBackground(Color.GREEN);
            clock.setOpaque(true);
            clock.setHorizontalAlignment(JLabel.CENTER);
            jf.add(clock,"Center");
            jf.add(pause,"North");
            jf.setSize(140,80);
            jf.setLocation(500,300);
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);
    
            MyThread3 mt = new MyThread3(clock,10000);
            mt.start();
            MyListener ml = new MyListener(clock,mt);
            pause.addActionListener(ml);
        }    
    }
    
    class MyThread3 extends Thread{
        private JLabel clock;
        private long time;
        private long end;
        
        public MyThread3(JLabel clock,long time){
            this.clock = clock;    
            this.time = time;
        }
        public void init(){
            long start = new Date().getTime();
            end = start + time;
        }    
        public void run(){
            this.init();
            while(true){
                long now = new Date().getTime();
                time = end - now;
                if(time > 0){
                    String s = this.convert(time);    
                    clock.setText(s);
                }else{
                    break;    
                }
                try{
                    Thread.sleep(10);    
                }catch(InterruptedException e){
                    e.printStackTrace();    
                }
            }
            clock.setText("时间到!");    
            clock.setBackground(Color.RED);
        }    
        public String convert(long time){
            long h = time / 3600000;
            long m = (time % 3600000) / 60000;
            long s = (time % 60000) / 1000;
            long ms = (time % 1000) / 10;
            String ph = h<10 ? "0":"";
            String pm = m<10 ? "0":"";
            String ps = s<10 ? "0":"";    
            String pms = ms<10 ? "0":"";    
            String txt = ph + h + ":" + pm + m + ":" + ps + s + "." + pms + ms; 
            return txt;
        }    
    }
    
    class MyListener implements ActionListener{
        private JLabel clock;
        private MyThread3 mt;
        private boolean running= true;
        
        public MyListener(JLabel clock,MyThread3 mt){
            this.clock = clock;
            this.mt = mt;
        }    
        public void actionPerformed(ActionEvent e){
            if(!mt.isAlive())
                return;
            JButton jb = (JButton)(e.getSource());
            if(running){
                jb.setText("Replay");
                clock.setBackground(Color.YELLOW);
                mt.suspend();
            }else{
                jb.setText("Pause");
                clock.setBackground(Color.green);
                mt.init();
                mt.resume();
            }
            running = !running;
        }    
    }

    测试结果:

    5      6      7

    13.终止线程

    一种特别优雅的方式终止线程(设置标志 flag)

    实例:

    public class Test {
        public static void main(String args[]){
            Runner r = new Runner();
            Thread t = new Thread(r);
            t.start();
    
    
            for(int i=0;i<10;i++){
                try{
                    Thread.sleep(5);
                    System.out.println("\nin thread main i=" + i);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("Thread main is over");
            r.shutDown();
        }
    }
    public class Runner implements Runnable {
        private boolean flag=true;
    
        public void run() {
            int i = 0;
            while (flag == true) {
                System.out.print(" " + i++);
            }
        }
    
    
        public void shutDown() {
            flag = false;
        }
    }

    main线程结束之后,子线程也就结束了(flag == false)

    14.线程控制的基本方法

    8

    15. 临界资源问题

    临界资源问题实例:

    两个线程A和B在同时操纵Stack类的同一个实例(栈),A向栈里push一个数据,B则要从堆栈中pop一个数据。

    栈的模拟
    
    public class Stack{
            int idx=0;
            char[ ] data = new char[6];
    
    
            public void push(char c){
                data[idx] = c;
                idx++;
            }
            public char pop(){
                idx--;
                return data[idx];
            }
        }

    可能出现的问题

    9

    (4 中 c 应该是不存在的)

    16. 互斥锁 和 死锁

    在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

    每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

    关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。

    synchronized用法

    ①用于方法声明中,标明整个方法为同步方法:这个时候调用这个方法的对象是处于被当前的线程锁定的状态

        public synchronized void push(char c){
                    data[idx] = c;
                    idx++;
            }

    ②用于修饰语句快,标明整个语句块为同步块:这个时候 synchronized  中的对象是被当前的线程锁定了的

        public char pop(){
                //其它代码
                synchronized(this){
                        idx--;
                        return data[idx];
                }
                //其它代码
        }

    并发运行的多个线程间彼此等待、都无法运行的状态称为线程死锁。

    线程死锁实例一:

    package v512.chap16;
    public class TestDeadLock{
        public static void main(String args[]){
            StringBuffer sb = new StringBuffer("ABCD");
            MyThread4 t = new MyThread4(sb);
            t.start();    
            synchronized(sb){
                try{
                    t.join();
                }catch(InterruptedException e){
                    e.printStackTrace();    
                }
                System.out.println(sb);    
            }
            System.out.println("Main thread is over!");    
        }    
    }
    
    class MyThread4 extends Thread{
        private StringBuffer sb;
        public MyThread4(StringBuffer sb){
            this.sb = sb;
        }
        public void run(){
            synchronized(sb){
                sb.reverse();
            }
            System.out.println("Sub thread is over!");    
        }    
    }

    控制台什么都没有输出来!

    分析:在main方法的同步块中串行插入了子线程t,并锁定了sb对象,然而子线程的run方法中也有一个同步块,它要锁定的对象正是sb对象,

    然而sb对象已经被主线程给锁定了,子线程只能等待,另一方面,主线程又必须要等到子线程结束了之后才可以执行,两者就这么一直等待着,

    陷入了死锁的状态!

    如果删除掉main中的同步,就可以看到结果:

    Sub thread is over!
    DCBA
    Main thread is over!

    实例二:

    package v512.chap16;
    public class TestDeadLock2{
        public static void main(String args[]){
            char[] a = {'A','B','C'};
            char[] b = {'D','E','F'};
            MyThread5 t1 = new MyThread5(a,b);
            MyThread5 t2 = new MyThread5(b,a);
            t1.start();
            t2.start();        
        }    
    }
    
    class MyThread5 extends Thread{
        private char[] source;
        private char[] dest;
        
        public MyThread5(char[] source,char[] dest){
            this.source = source;
            this.dest = dest;
        }
        public void run(){
            synchronized(source){
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();    
                }
                
                synchronized(dest){
                    System.arraycopy(source,0,dest,0,source.length);
                    System.out.println(dest);
                }        
            }
        }    
    }

    假设线程t1先执行run,然后将字符数组a锁定了,接着t1休眠了,线程t2开始执行,并将数组b锁定了,之后线程t2进入休眠,

    然后线程t1重新执行,这时候发现数组b已经被线程t2给锁定了,只能等待,休眠之后,线程t2同样发现数组a被线程t1给锁定了,

    两者相互之间等待着,就陷入了死锁状态!

    17.线程的同步通信

    为避免死锁,在线程进入阻塞状态时应尽量释放其锁定的资源,以为其他的线程提供运行的机会。

    相关方法:

    public final void wait() :会导致当前的线程进入阻塞状态并尽量释放其锁定的资源

    public final void notify():将某个线程重新恢复过来,但不是马上就进入到就绪状态,它可能还要等,等到它要使用的对象解除了锁定才行

    public final void notifyAll()

    10

    生产者 和 消费者 问题:

    同步的栈:

    package v512.chap16;
    public class SyncStack { // 支持多线程同步操作的堆栈的实现
        private int index = 0;
        private char[] data = new char[6];
    
        public synchronized void push(char c) {
            while (index == data.length) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            this.notify();
            data[index] = c;
            index++;
            System.out.println("produced:" + c);
        }
    
        public synchronized char pop() {
            while (index == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            this.notify();
            index--;
            System.out.println("consumed:" + data[index]);
            return data[index];
        }
    }

    测试类:包括了生产者和消费者

    package v512.chap16;
    public class SyncTest{
        public static void main(String args[]){
            SyncStack stack = new SyncStack();
            Runnable p=new Producer(stack);
            Runnable c = new Consumer(stack);
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
            t1.start();
            t2.start();
        }
    }
    
    
    class  Producer implements Runnable{
        SyncStack stack;    
        public Producer(SyncStack s){
            stack = s;
        }
        public void run(){
            for(int i=0; i<20; i++){
                char c =(char)(Math.random()*26+'A');
                stack.push(c);
                try{                                    
                    Thread.sleep((int)(Math.random()*300));
                }catch(InterruptedException e){
                }
            }
        }
    }
    
    class Consumer implements Runnable{
        SyncStack stack;    
        public Consumer(SyncStack s){
            stack = s;
        }
        public void run(){
            for(int i=0;i<20;i++){
                char c = stack.pop();
                try{                                       
                    Thread.sleep((int)(Math.random()*500));
                }catch(InterruptedException e){
                }
            }
        }
    }

    输出结果:

    produced:M
    consumed:M
    produced:N
    consumed:N
    produced:P
    produced:A
    consumed:A
    consumed:P
    produced:Z
    consumed:Z
    produced:F
    produced:J
    produced:X
    produced:Q
    consumed:Q
    produced:N
    produced:W
    produced:K
    consumed:K
    produced:I
    consumed:I
    produced:A
    consumed:A
    produced:Z
    consumed:Z
    produced:M
    consumed:M
    produced:X
    consumed:X
    produced:X
    consumed:X
    produced:P
    consumed:P
    produced:V
    consumed:V
    consumed:W
    consumed:N
    consumed:X
    consumed:J
    consumed:F

    18.多线程编程专题  (还未仔细看)

    线程间数据传输

    例16-16 使用管道流实现线程间数据传输

    类的同步性与线程安全

    例16-17 验证同步类的线程安全性

    定时器

    例16-18 使用定时器实现数字时钟功能

  • 相关阅读:
    爬取某APP的数据
    GPU操作
    Beyond过期处理方法
    tensorRT安装
    YOLOX multiGPU training
    linux服务器环境部署(三、docker部署nginx)
    linux服务器环境部署(二、docker部署jhipster-registry)
    linux服务器环境部署(一、docker部署)
    CAP原则
    热部署live-server
  • 原文地址:https://www.cnblogs.com/yinger/p/2162621.html
Copyright © 2011-2022 走看看