黑马程序员——java基础---多线程(一)

  学习多线程之前的一些基本概念认知:

    进程:进程是动态的、是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

    线程:线程依附于进程,可以理解为进程下的一个子执行路径,但没有进程线程无法单独执行。

    两者间的区别:进程的运行需要给它分配独立的地址空间和系统资源等。不同进程的数据和状态都是完全独立的,所以不同进程之间的通信或转换很不易;而线程执则是,行过程中所需的系统资源由进程进行统一分配,所以同一进程的不同线程执行过程中是会相互影响的,而且线程本身的数据通常放于寄存器或者堆栈中,所以线程切换的难度要比进程切换小的多。

    单线程:如果某个程序执行过程中,进程下只有唯一的一条线程,那么这便是单线程。

    多线程:如果某个程序执行过程中,进程下有不同的线程在执行各自的任务,那么这就是多线程。

  了解了以上概念,那么第一个问题就出现了。计算机为什么要使用多线程来执行任务呢?答案很简单,因为多线程单位时间内执行的任务量比单线程大,而且对计算机CPU而言,单线程无疑会造成CPU的极度浪费。

 

  多线程在Java中的体现:

     1、与其它大多数编程语言不同,Java本身内置支持多线程编程。

      2、程序运行过程中,包含两条或两条以上并发运行的部分,即为多线程。每个单独部分都可称之为线程,每个线程都有独立的执行路径。

  多任务处理主要有两种不同的类型:基于进程和基于线程。

    基于进程:进程本质上是一个执行的程序。所以说基于进程就是指可以同时运行多个程序。例如:我们可以一边听音乐,一边在QQ上聊天。

    基于线程:这种方式中,线程才是最小的执行单位。而线程依赖于进程。可以理解为一个程序可以同时执行多个任务。例如:我们打开一个QQ软件,可以同时和多个人聊天。

 

  线程的一些状态以及不同状态间的转换方式:

  Java中的线程的五种状态:

       1、新建:用new关键字来创建Thread类或者其子类的对象。

    2、就绪:线程创建后,通过别的线程调用它的start()方法,使该线程就进入就绪状态。

    3、运行:就绪状态状态的线程获得了CPU的使用权,便会进入运行状态。

    4、阻塞:运行状态的线程,因为某种原因,从而无法取得CPU使用权,便进入了阻塞状态。

    5、消亡:处于运行状态的线程,执行完run()方法后,自动释放CPU是使用权,就变成了消亡状态。

  不同状态间的转换:

  一、创建并运行线程

      通过new创建线程后,调用其start()方法,使其执行重写的run()方法,从而进入运行状态。运行中的的线程可以使用Thread类中的isAlive()方法来判别线程的运行状态。如果isAlive()返回true,线程便处于运行状态;当isAlive()返回false,线程可能处于等待状态,也可能处于停止状态。

  二、挂起和唤醒线程
    线程执行过程中,可以通过两个方法使线程暂时停止执行,它们分别是suspend()和sleep()。在使用suspend()挂起线程后,可以通过resume()方法来重新唤醒线程;而使用sleep()使线程进入休眠后,只能等待设定的时间过后,线程才会恢复成就绪状态。虽然suspend()和resume()可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为‘弃用’标记。所以新写的程序中,尽量不要使用这两个方法来操作线程。

  三、终止线程的三种方法:
    1、设置退出标志,使线程正常退出,即当run()方法完成后线程终止。
    2、使用stop()方法强行终止线程。这个和上面两个方法一样,不推荐使用。
    3、通过interrupt()方法中断线程。

 

  Java中线程的创建:

    一、继承Thread类

    步骤:1、定义类继承Thread  2、复写Thread类的run()方法  3、创建新定义类的对象  4、调用start()方法启动线程

代码实现:

 

 1 package come.file;
 2 
 3 //1、定义TestThread类继承Thread类
 4 class TestThread extends Thread{
 5     //2、复写Thread类的run()方法
 6     public void run(){
 7         System.out.println("这是TestThread类");
 8     }
 9 }
10 public class MyThread{
11     public static void main(String[] args){
12         //创建新定义的TestThread类的对象
13         TestThread t = new TestThread();
14         //调用start()方法
15         t.start();
16     }
17 }

  二、实现Runnable接口

  步骤:1、创建类实现Runnable接口 2、复写run()方法  3、调用new Thread(runnable)方式创建线程  4、调用start()方法启动线程

代码实现:

 

 1 package come.file;
 2 
 3 //1、创建类实现Runnable接口
 4 class TestThread2 implements Runnable {
 5     //2、复写run()方法
 6     public void run(){
 7         System.out.println("这是TestThread2类");
 8     }
 9 }
10 public class MyThread2 {
11     public static void main(String[] args) {
12         //3、调用new Thread(runnable)方式创建线程
13         Thread t2 = new Thread(new TestThread2());
14         //4、调用start()方法启动线程
15         t2.start();
16     }
17 }

 

  注意:

    1、在继承Thread的方式中,可以使用getName()方法,来获得当前线程的名字。而在实现Runnable方式中,却不可以使用this.getName(),因为Runnable接口没有这个方法,所以只能通过Thread的静态方法Thread.currentThread()来获取当前的Thread对象,再通过调用getName()方法,来取得当前线程的名字。

    2、对于Java来说,run()方法并没有任何特别之处。像main()方法一样,它只为了让新线程知道需要调用的方法的名称。因此,在Runnable上或者Thread上调用run()方法是合法的,但并不启动新的线程。只有通过调用start()方法才会启动新线程。

  通过对比两种方式,我们可以发现Runnable接口方式要优于继承Thread类方式。因为前者实现了接口后还可以继承其他的类,而后者如果有了本身的父类,想要在使用多线程运行某段代码会比较麻烦。Runnable接口这种方式,非常适合多个相同线程来处理同一份资源这种情况,可以更准确的体现面向对象这种思维模式。

  多线程的运行会出现安全问题:

    问题原因:当多条语句操作同一个线程共享数据时,一个线程对只执行了一部分,还没有执行完,另一个线程便参与了进来,这就会导致共享数据的错误。

    解决办法:对多条操作共享数据的语句,只能让一个线程执行完,执行过程中其它的线程无法参与进来。

 

  Java对于多线程的的安全问题提供了专业的解决方式——synchrozied

  代码模型:obj相当于锁,持有锁的线程可以在同步中执行,没有锁的线程即使有cpu执行权,也无法进入同步中执行。

 

synchronized(obj){
    //需要同步的代码
} 

  同步的前提:

    1、必须有两个或两个以上的线程。
    2、必须是多个线程使用同一个锁,必须保证同步中只有一个线程在运行。    

  好处:解决了多线程的安全问题。
  弊端:多一重判断消耗资源。

    如何找到需要同步的代码?
    1、明确那些代码是多线程代码。
    2、明确共享数据。
    3、明确多线程运行代码那些事操作共享数据的。

  同步函数中的this相当于锁,静态同步函数中的所属类的字节码对象相当于锁。

  两种单例模式:

  饿汉式代码:

 

1 class Single{
2     private static final Single s = new Single();
3     private single(){}
4     public static Single getInstance(){
5         return s;
6     }
7 }

    懒汉式代码:

 1 class Single{
 2     private static Single s = null;
 3     private Single(){ }
 4 
 5     public static Single getInstance(){
 6         if(s==null){
 7             synchronized(Single.class){
 8                 if(s==null)
 9                     s = new Single();
10             }
11         }
12         return s;
13     }
14 }

 

   当多线程中有两把锁,便有可能产生死锁问题,例如以下代码:

 1 class TestRun implements Runnable{
 2     private boolean flag;
 3     TestRun(boolean flag){
 4         this.flag = flag;
 5     }
 6     public void run(){
 7         if(flag){
 8             synchronized (Lock.lockA) {
 9                 System.out.println(new Thread().currentThread().getName()+"获得lockA");
10                 synchronized (Lock.lockB) {
11                     System.out.println(new Thread().currentThread().getName()+"获得lockB");
12                 }
13             }
14         }else{
15             synchronized (Lock.lockB) {
16                 System.out.println(new Thread().currentThread().getName()+"获得lockB");
17                 synchronized (Lock.lockA) {
18                     System.out.println(new Thread().currentThread().getName()+"获得lockA");
19                 }
20             }
21         }
22     }
23 }
24 class Lock{
25     static Object lockA = new Object();
26     static Object lockB = new Object();
27     
28 }
29 public class DeadLockDemo {
30     public static void main(String[] args) {
31         TestRun t1 = new TestRun(true);
32         TestRun t2 = new TestRun(false);
33         new Thread(t1).start();
34         new Thread(t2).start();
35     }
36 
37 }

 

  所以,在编写多线程代码时,应尽量避免死锁的产生。

  

posted @ 2015-07-13 23:17  shadowW_W  阅读(166)  评论(0编辑  收藏  举报