• (原创)JAVA多线程一传统多线程


    一,多线程

      多线程是提高程序效率,避免资源浪费的很好的解决方案,下面来慢慢的介绍多线程的一些基本知识,而这些是晋级高级不可或缺的一部分

      1,Thread类

      类实现多线程需要实现Runnable接口,我们跟踪一下源码,如下所示:

    public
    class Thread implements Runnable {
        /* Make sure registerNatives is the first thing <clinit> does. */
        private static native void registerNatives();
        static {
            registerNatives();
        }
    ........
    }

    Thread类实现了Runnable的接口,然后我们来看一下Runnable接口里面有什么东西,如下所示,哦,原来就一个run方法

    public
    interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p> 意思是说,实现runnable的接口后的对象可以用来创建一个线程,并且这个线程是通过run方法来运行的
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }

    那么回过头来看看,Thread类中的重写的这个run方法是什么?

        /**注意,这个注释的意思是说当用一个单独的Runnable对象来构造Thread线程的时候,Runnbalb对象的run方法将会被调用,否则,这个方法不起任何作用
         * If this thread was constructed using a separate 
         * <code>Runnable</code> run object, then that
         * <code>Runnable</code> object's <code>run</code> method is called;
         * otherwise, this method does nothing and returns.
         * <p>
         * Subclasses of <code>Thread</code> should override this method.
         *
         * @see     #start()
         * @see     #stop()
         * @see     #Thread(ThreadGroup, Runnable, String)
         */
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }

    那么,这个target是什么鬼?继续追踪

        /* What will be run. */
        private Runnable target;

    好了,上面说的是背景知识,下面来真材实料吧。

    初始化线程的方法有两个,

    1,直接重写Thread类的run方法,注意,如果此时给Thread的构造函数传递一个Runnable对象,这个runnable对象的run方法不起任何作用,因为重写的run方法没有调用父类的run方法

    2,直接给Thread传递一个runnable对象,调用对象的run方法

    方法1:

            Thread tr = new Thread(){
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("thread test");
                    }
                }
            };
            tr.start();
    View Code

    方法2:

            Thread tr2 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("runnable test");
                    }
                }
            }){};
            tr2.start();
    View Code

    针对方法1,我们这有一个小小的验证

    当我们同时传递一个runnable对象和重写run方法时,结果会怎样呢?很明显,重写的run方法后,不会调用原来的那个target相关的操作了,也就是说传递进去的runnable没任何作用了,下面验证一下:

            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("in runnbale");
                    }
                }
            }){
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println("in thread");
                    }
                }
            }.start();
        }
    View Code

    运行结果是:

    in thread
    in thread
    in thread
    in thread
    in thread
    in thread
    in thread

     二,定时器

     这里主要介绍

    Timer和TimerTask两个类

    Timer类是声明一个定时器类,声明的时候可以给这个类提供一个TimerTask类的对象,注意,我们追踪一下这个TimerTask类,看到源码如下:

    public abstract class TimerTask implements Runnable { 
      public abstract void run();
    ................
    }

    也就是说,这个类继承了runnable接口,意思就是说这个类最终是要生成一个Thread来进行TimerTask里面的run方法的。由此可见,定时器就是定时生成一个新的线程,然后这个新线程来执行run方法里的操作来的。

    至于Timer里不同的构造参数,可以参考sdk里面的相关内容来学习。定时器就讲到这里了。

    三,线程互斥

    在实际生活中,我们会出现很多线程使用一个资源的问题,这些线程可能会抢夺资源,或者在某一时间段内,只能一个线程来使用资源,这个时候我们就需要用synchronized来进行线程间的互斥,下面来介绍一下线程互斥的问题

    首先,我们建一个类,或者在一个类里将可能共同访问的资源给他一把锁,锁住这个资源,如下所示

        public class test {
            public void displayit(String name) {
                synchronized (this) {
                    for (int i = 0; i < name.length(); i++) {
                        System.out.print(name.charAt(i));
                    }
                    System.out.println();
                }
            }
        }

    这个锁就是synchronized,锁住的是test这个对象,也就是说对这个对象的访问都需要看看这个对象是否已经被锁住

    下面,我们开两个线程,来一直不停的访问这个对象,并输出访问内容,如下所示

        public void init() {
            final test2 t = new test2();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(10);
                            t.displayit("leeyangyang");
                            
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(10);
                            t.displayit("weijunjun");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    View Code

    然后我们在main方法里调用这个方法

    public class Sync {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            new Sync().init();
        }
    ..............................
    }

    最后我们得到的结果是:

    leeyangyang
    weijunjun
    leeyangyang
    weijunjun

    然后再引伸一下,如果传递进去的是静态类,那么锁住的应该是什么参数呢?很明显,答案是静态类.class,具体的如下所示:

        public static class test2 {
            public void displayit(String name) {
                synchronized (test2.class) {
                    for (int i = 0; i < name.length(); i++) {
                        System.out.print(name.charAt(i));
                    }
                    System.out.println();
                }
            }
        }

    好了,线程互斥就介绍到这里了。

    四,线程同步通信

    进程间的通信是通过wait()和notify()来实现的,当等待一个资源时,用wait()方法,使用后,注意将资源释放或者重新标注一下,如下面所示,要实现的是子线程跑10次,然后主线程跑10次,然后子线程再跑10次,主线程跑10次,这样,一共持续50遍。

    思路:

    1,先实现主线程和子线程都跑10次50遍,

    2,根据互斥规则,跑得时候,两者互不打扰

    3,给一个信号,当子线程正在执行的时候,主线程会在等待,子线程执行完之后,通知一下等待的线程,然后主线程得到这个通知后,开启线程。

    看下面代码:

    public class Synch2 {
        public static void main(String[] args) {
            new Synch2().init();
        }
    
        public void init() {
            final test2 t = new test2();
            // 现开启一个子线程,然后根据子线程
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    for (int i = 1; i <= 50; i++) {
                        t.sub(i);
                    }
                }
            }).start();
    
            for (int i = 1; i < 50; i++) {
                synchronized (Synch2.class) {
                    t.maintest(i);
                }
            }
        }
    
        public class test2 {
            boolean subUsed = true;
    
            public synchronized void sub(int i) {
                while (!subUsed) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 0; j < 10; j++) {
                    System.out.println("inn the subthread, num=" + j + " and loop "
                            + i);
                }
                subUsed = false;
                this.notify();// 通知所有的正在等待的线程
            }
    
            public synchronized void maintest(int i) {
                while (subUsed) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 0; j < 10; j++) {
                    System.out.println("inn the main thread, num=" + j
                            + " and loop " + i);
                }
                subUsed = true;
                this.notify();
            }
        }
    }
    View Code

    这里有一个细节,就是在等待信号的时候,我们使用的是while,其实if也可以,但是我们使用的是while,原因是

    可能其他原因导致了notify出现,但是while的条件仍不满足,这个时候,仍然会等待,但是如果是if的话,情况并不是这样

    五,线程共享变量

    线程内的变量在进行共享时,可以在外部新建一个map对,将变量放进去,然后,同一个线程内的对象可以共享变量

    具体的代码如下

    package cn.unis;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    
    public class SyncShare {
        final static Map<Thread , Object> maps = new HashMap<Thread, Object>();
        public static void main(String[] args) {
            new SyncShare().init();
        }
        
        public void init(){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    int data = new Random().nextInt(100);
                    maps.put(Thread.currentThread(), data);
                    new a().display();
                    new b().display();
                }
            }).start();
            new Thread(new Runnable(){
                @Override
                public void run() {
                    int data = new Random().nextInt(100);
                    maps.put(Thread.currentThread(), data);
                    new a().display();
                    new b().display();
                }
            }).start();
        }
        
        public static class a{
            public void display(){
                System.out.println(Thread.currentThread().getName()+" get a data is " + maps.get(Thread.currentThread()));
            }
        }
        
        public static class b{
            public void display(){
                System.out.println(Thread.currentThread().getName()+" get b data is " + maps.get(Thread.currentThread()));
            }
        }
    }
    View Code

    执行效果如下

    Thread-0 get a data is 73
    Thread-1 get a data is 89
    Thread-0 get b data is 73
    Thread-1 get b data is 89

     六,多线程访问共享对象和数据的方式

    分为两种情况,

    1,每个线程执行的代码相同,比如说售票系统等,可以使用同一个runnable对象,这个runnable对象中有那个共享数据

    2,每个线程执行的代码不同,比如说一个相加,一个相减,这个时候就需要不同的runnable对象,具体的可以分为下面几种数据共享的方式

    a,将共享数据封装带另外一个对象中,然后将这个对象逐一传递给各个runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据斤西瓜的每个操作的互斥和通信。

    b,将这些runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个runnable对象调用外部类的这些方法

    c,上面两种方法的组合,将共享数据封装到另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量货方法中的局部变量,每个线程的runnale 对象作为外部类中的成员内部类和局部内部类

    d,总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放到同一个类中,这样比较容易实现他们之间的同步互斥和通信

  • 相关阅读:
    无线渗透开启WPS功能的路由器
    写代码怎能不会这些Linux命令?
    分布式服务框架 Zookeeper -- 管理分布式环境中的数据
    每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)
    Innodb 中的事务隔离级别和锁的关系
    线上操作与线上问题排查实战
    MySQL 四种事务隔离级的说明
    一次由于 MTU 设置不当导致的网络访问超时
    SYN 和 RTO
    The story of one latency spike
  • 原文地址:https://www.cnblogs.com/ningheshutong/p/5837239.html
Copyright © 2020-2023  润新知