Java Thread入门殿堂

Java Thread入门殿堂


线程介绍

Thread,do you know?线程是程序执行的一条路径, 一个进程中可以包含多条线程;多线程并发执行可以提高程序的效率, 可以同时完成多项工作。线程在并发编程中非常重要,巧妙的使用多线程技术大大有利于程序的效率,但是线程的使用是有门槛的,用的不好适得其反,综上:掌握线程技术乃程序猿的必备灵药!


线程的创建

I 线程的创建常用的有两种方式:

方式一:继承Thread类,重写run方法。

方式二:实现Runnable接口,重写run方法。

这里只给出方式二的实现代码清单: 

Runnable底层源码分析:在Thread构造函数中传入Runnable实现对象,在Thread源码中将Runnable对象target传递给init方法,而在init方法中又将target传给的Thread类的成员变量this.target中,最后在run方法中,通过判断this.target是否为null而选择执行Runnable中的run方法与否。详细见下图:

II 匿名内部类实现:

方式三:匿名内部类实现Thread创建,通俗的来讲就是一种一次性使用的产物,写起来非常方便很适合实际开发中一次性使用。匿名内部类见下图:

III Callable实现 

Callable实现方法与Runnalbe相似,但是Callable实现方法有返回值,Callable中重写的是call方法,Callable常用在Java自带的线程池ExecutorService中,我将在本博客尾部介绍ExecutorService线程池,这里先简单列出Callable实现类,请在线程池中看具体使用。


线程命名

设置名字:①在创建的时候设置;②创建后利用thread.setName()设置;

获取名字:thread.getName()获取;Thread.CurrentThread().getName()获得当前线程的名字。

下图为线程的命名方法截图:


线程操作(阻塞、激活、同步等)

(1)守护线程

守护线程:利用setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出。【注】:守护线程要在线程启动前设置才有效哟!
  Thread t1 = new Thread() {
      public void run() {
          for(int i = 0; i < 50; i++) {
             System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                try {
                   Thread.sleep(10);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
            }
      }
 };
 t1.setDaemon(true);                        //将t1设置为守护线程
 t1.start();

(2)线程暂停join&yeild

线程暂停:* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续;join(int), 可以等待指定的毫秒之后继续。                    * yeild(),又称礼让线程,使当前线程让出CPU,但仍有机会被CPU分配时间片进入就绪队列。
            final Thread t1 = new Thread() {
                public void run() {
                    for(int i = 0; i < 50; i++) {
                        System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }; 
            Thread t2 = new Thread() {
                public void run() {
                    for(int i = 0; i < 50; i++) {
                        if(i == 2) {
                            try {
                                //t1.join();                        //插队,加入(t2停下来等待t1执行完毕
                                t1.join(30);                      //加入,有固定的时间,过了固定时间,继续交替执行
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(getName() + "...bb");
                    }
                }
            };
            t1.start();
            t2.start();

补充:setPriority()设置线程的优先级,但是究竟起没起作用还要看JVM的心情哟!

(3)线程同步&死锁

I:线程同步

  什么情况下需要同步
    * 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    * 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  同步代码块
    * 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    * 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

            class Printer {
                Demo d = new Demo();
                public static void print1() {
                    synchronized(d){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                        System.out.print("C");
                        System.out.print("S");
                        System.out.print("D");
                        System.out.print("N");;
                        System.out.print("\r\n");
                    }
                }
    
                public static void print2() {    
                    synchronized(d){    
                        System.out.print("博");
                        System.out.print("客");
                        System.out.print("知");
                        System.out.print("飞");                                                                                                                                                              System.out.print("翀");
                        System.out.print("\r\n");
                    }
                }
            }

【注】:使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的。非静态同步函数的锁是:this; 静态的同步函数的锁是:字节码对象。

    代码清单:

 class Printer {
   public static void print1() {
       synchronized(Printer.class){                
        //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
         System.out.print("C");
         System.out.print("S");
         System.out.print("D");
         System.out.print("N");
         System.out.print("\r\n");
       }
    }
    public static synchronized void print2() {    
         System.out.print("知");
         System.out.print("飞");
         System.out.print("翀");
         System.out.print("\r\n");
        }
    }

II:死锁

多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁,所以要尽量不要嵌套使用。
            private static String s1 = "筷子左";
            private static String s2 = "筷子右";
            public static void main(String[] args) {
                new Thread() {
                    public void run() {
                        while(true) {
                            synchronized(s1) {
                                System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                                synchronized(s2) {
                                    System.out.println(getName() + "...拿到" + s2 + "开吃");
                                }
                            }
                        }
                    }
                }.start();
                
                new Thread() {
                    public void run() {
                        while(true) {
                            synchronized(s2) {
                                System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                                synchronized(s1) {
                                    System.out.println(getName() + "...拿到" + s1 + "开吃");
                                }
                            }
                        }
                    }
                }.start();
            }
 

(4)线程安全

* 多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

常用Java类的线程安全性

Vector是线程安全的, ArrayList是线程不安全的;
StringBuffer是线程安全的, StringBuilder是线程不安全的;
Hashtable是线程安全的, HashMap是线程不安全的;


线程中的单例设计模式

Singleton设计模式:保证类在类村中只有一个对象,该模式的关键在于:(1)构造方法私有private;(2)在本类中定义一个本类对象,Singleton s;(3)提供公共的访问方法,public static Singleton getInstance(){ return s; }

单例模式有以下三种实现方式:(这对之后的框架学习非常有用,千万理解!) 

方式一:饿汉式(类加载及创建Singleton s----导致程序启动较慢但运行较快)

方式二:懒汉式又称单例的延迟加载模式(使用到类对象时才创建Singleton s----导致程序启动较快但运行较慢)

方式三:final定义(理解:final修饰的变量无法被修改)

Runtime类

java中Runtime类就是一个饿汉单例类,源码截图如下:

 

Runtime类的使用(实现向DOS窗口下输入命令的效果):

   Runtime r = Runtime.getRuntime();
   //r.exec("shutdown -s -t 300");    //300秒后关机
   r.exec("shutdown -a");                //取消关机


计时器(Timer类)

Timer类:用来计时的java类,可以定时开启任务线程,并设置间隔多长时间启动一次。代码清单如下图:

 


多线程通信

 两个线程通信:

1.什么时候需要通信
    * 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    * 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
* 2.怎么通信
    * 如果希望线程等待, 就调用wait()
    * 如果希望唤醒等待的线程, 就调用notify();
    * 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

三个及以上通信:
    * notify()方法是随机唤醒一个线程
    * notifyAll()方法是唤醒所有线程
    * JDK5之前无法唤醒指定的一个线程
    * 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件(通知无法指定到那个线程上只有while能读到)

线程的生命周期 


互斥锁(JDK1.5新特性)

1.同步
    * 使用ReentrantLock类的lock()和unlock()方法进行同步
* 2.通信
    * 使用ReentrantLock类的newCondition()方法可以获取Condition对象
    * 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
    * 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

【注】:JDK1.5中使用Reentrant来实现同步与通信,比synchronized&wait/notify更加高效,但是写起来更加麻烦;Condition可以理解为具有标识的wait/notify,它可以通知到某个具体Condition头上来,比wait/notify更加高效。


线程组

线程组概述
    * Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    * 默认情况下,所有的线程都属于主线程组。
        * public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
        * public final String getName()//通过线程组对象获取组的名字
    * 我们也可以给线程设置分组
        * 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
        * 2,创建线程对象
        * 3,Thread(ThreadGroup group, Runnable target, String name) 
        * 4,设置整组的优先级或者守护线程
案例演示
        * 线程组的使用,默认是主线程组
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr, "张三");
        Thread t2 = new Thread(mr, "李四");
        //获取线程组
        // 线程类里面的方法:public final ThreadGroup getThreadGroup()
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();
        // 线程组里面的方法:public final String getName()
        String name1 = tg1.getName();
        String name2 = tg2.getName();
        System.out.println(name1);
        System.out.println(name2);
        // 通过结果我们知道了:线程默认情况下属于main线程组
        // 通过下面的测试,你应该能够看到,默认情况下,所有的线程都属于同一个组
        System.out.println(Thread.currentThread().getThreadGroup().getName());

自己定义线程组          
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("这是一个新的组");

        MyRunnable mr = new MyRunnable();
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, mr, "张三");
        Thread t2 = new Thread(tg, mr, "李四");
        
        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());
        
        //通过组名称设置后台线程,表示该组的线程都是后台线程
        tg.setDaemon(true);


Java内置线程池

线程池概述
    * 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
内置线程池的使用概述
    * JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
        * public static ExecutorService newFixedThreadPool(int nThreads)
        * public static ExecutorService newSingleThreadExecutor()
        * 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
        * Future<?> submit(Runnable task)
        * <T> Future<T> submit(Callable<T> task)
使用步骤:
        * 创建线程池对象
        * 创建Runnable实例
        * 提交Runnable实例
        * 关闭线程池

下面以求前n项和为例引入ExecutorService&Callable来实现:

 


                                                                  谢谢阅读                   ----知飞翀

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值