【Java基础】Java多线程小结

  在说多线程之前,首先要清楚为啥要提出多线程,这就要明白线程和进程间的区别了。

线程和进程间的区别

  是具有一定独立功能的程序于某个数据集合上的一次运行活程是统进源分配和度的一个独立
  线程的一个,CPU度和分派的基本,它是比程更小的能独立运行的基本.线程自己基本上不有系统资,有一点在运行中必不可少的(如程序数器,寄存器和),但是它可与同属一个程的其他的线程共享程所有的全部
  一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行,相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列
  进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,然而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。多个线程共享内存,从而极大地提高了程序的运行效率。

 

Java线程的生命周期

Java线程的生命周期分为如下几种:

  新建:创建线程对象
  就绪:拥有执行资格,但是没有执行权
  运行:有执行资格且有执行权
  阻塞:没有执行资格和执行权,但是可以被激活到就绪
  死亡:线程对象变为垃圾,等待被回收。

如何创建Java线程

  在Java中,可以通过继承Thread类或者实现Runnable接口来定义一个线程类,于此同时,要重写run方法。当然在继承Thread的时候由于Thread有run方法的默认实现,所以不重写就是执行父类Thread的run方法,实现Runnable必须要重写run方法。另外,之所以定义run方法,是为了申明只有run方法中的代码才会被线程执行,下面就是最简单的定义线程类的例子:

  

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("test Thread!");
    }
}
public class RunnableTest implements Runnable {

    @Override
    public void run() {
        System.out.println("Test Runnable");
    }
}

两类线程的启动方式有点区别,如下代码所示:

public class ThreadTest {
    public static void main(String args[]) {
        MyThread myThread = new MyThread();
        myThread.start();

        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();

    }
}

可以看出,实现Runnable的线程类的启动方式是包在Thread的构造方法中,然后还是调用Thread的start方法启动。

 

如何选择那种实现方式

  在实际定义线程类时,到底选那种,这个时候就需要对比下两者的区别了。Thread的是一个类,其中该类实现了Runnable接口,从类和接口的区别可以看出,继承Thread类就会有单继承的局限;另外,从启动线程来看,继承Thread的类需要每次都new一个对象然后启动,而同一个实现Runnable的线程类可以包在多个Thread构造方法中去启动多线程,这样利于线程间的数据共享。总结起来就是:

  1. Runnable能避免点继承的局限,一个类可以继承多个接口。
  2. Runnable适合于资源的共享

  结论是:开发中一般都多用实现Runnable这种方式,第一避免单继承局限,第二利于数据共享

 

线程构造方法和重要方法的介绍

由于Runnable是一个接口,并且该接口定义了一个run方法,且该run方法都会被重写,所以只讲解Thread类的构造方法和重要方法,首先来看构造方法:

Thread() 
          分配新的 Thread 对象。
Thread(Runnable target) 
          分配新的 Thread 对象。
Thread(Runnable target, String name) 
          分配新的 Thread 对象。
Thread(String name) 
          分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target) 
          分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target, String name) 
          分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 
          分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。
Thread(ThreadGroup group, String name) 
          分配新的 Thread 对象。

  从上述构造方法可以看出,可以通过构造方法设置Thread对象的名字,同时还能设置线程对象的线程组和堆栈大小等。下面是几个重要的方法:

static Thread    currentThread() 
          返回对当前正在执行的线程对象的引用。
long    getId() 
          返回该线程的标识符。
String    getName() 
          返回该线程的名称。
int    getPriority() 
          返回线程的优先级。
Thread.State    getState() 
          返回该线程的状态。
ThreadGroup    getThreadGroup() 
          返回该线程所属的线程组。
void    interrupt() 
          中断线程。
static boolean    interrupted() 
          测试当前线程是否已经中断。
boolean    isAlive() 
          测试线程是否处于活动状态。
boolean    isDaemon() 
          测试该线程是否为守护线程。
boolean    isInterrupted() 
          测试线程是否已经中断。
void    join() 
          等待该线程终止。
void    join(long millis) 
          等待该线程终止的时间最长为 millis 毫秒。
void    setDaemon(boolean on) 
          将该线程标记为守护线程或用户线程。
void    setName(String name) 
          改变线程名称,使之与参数 name 相同。
void    setPriority(int newPriority) 
          更改线程的优先级。
static void    sleep(long millis) 
          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
void    start() 
          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
static void    yield() 
          暂停当前正在执行的线程对象,并执行其他线程。
  • currentThread,getName,getID 方法

下面来测试每个方法,在前面谈线程和进程的关系中谈到一个线程可以启动和撤销另外的线程。在启动两种类型的线程中,说明还有一个背后的线程在启动这两个线程,那这个线程是谁呢?我们可以通过以上的前3个方法得知一二:

public class ThreadTest {
    public static void main(String args[]) {
        String threadName = Thread.currentThread().getName();
        long threadID = Thread.currentThread().getId();

        System.out.println(threadID+ ": " + threadName);
    }
}

//1: main
View Code

从结果看出,这是1号线程main线程。那么既然可以获取线程的名字,那么我们也可以手动设置线程的名字,如下测试代码:

package cn.Thread;

/**
 * Created by lili on 15/12/6.
 */
public class ThreadTest {
    public static void main(String args[]) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.setName("Thread1");
        t2.setName("Thread2");
        t1.start();
        t2.start();
    }

}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+": "+ i);
        }
    }

}
View Code

运行结果则如下:

Thread1: 0
Thread1: 1
Thread1: 2
Thread1: 3
Thread1: 4
Thread1: 5
Thread1: 6
Thread1: 7
Thread1: 8
Thread1: 9
Thread2: 0
Thread2: 1
Thread2: 2
Thread2: 3
Thread2: 4
Thread2: 5
Thread2: 6
Thread2: 7
Thread2: 8
Thread2: 9
View Code
  • join方法
package cn.Thread;

/**
 * Created by lili on 15/12/6.
 */
public class ThreadTest {
    public static void main(String args[]) {
        MyRunnable myRunnable = new MyRunnable();
        MyRunnable1 myRunnable1 = new MyRunnable1();
        Thread t1 = new Thread(myRunnable1);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);
        Thread t4 = new Thread(myRunnable);

        t1.setName("Thread1");
        t2.setName("Thread2");
        t3.setName("Thread3");
        t4.setName("Thread4");
        t3.setPriority(Thread.MAX_PRIORITY);
        t4.start();
        t1.start();
        t2.start();
        System.out.println("当前活动线程的个数:" + Thread.activeCount());
        System.out.println(Thread.getAllStackTraces());
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        t3.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class MyRunnable1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

  为了获得join的效果,将join线程的循环次数设置的最小,循环次数为5,后启动的3号线程设置最高的优先权。

  在代码中,启动1,2,4后,打印active线程的个数为5,然后再打印stackTrace,这时候main,1,2,4号线程已经是active状态。最终的运行结果是t1,t2,t4先交替抢得执行权执行,待join线程1结束后,剩余线程交替执行。其中在执行t1.join()之前,main线程其实已经启动了,但是main线程最终是等到1执行完毕才开始执行。另外,后启动的t3是等线程1结束了才开始执行。所以从代码执行结果可以看出,join的作用是:

  1. 让主线程等待join子线程执行结束再继续执行;
  2. 让join线程之后启动的线程也等待join线程执行结束再启动执行。

  基于上述两个结论,join 的用法也比较明显,join可以让后续代码获得利用join线程的执行结果。

main: 0
Thread3: 0
Thread3: 1
Thread2: 6
Thread4: 6
main: 1
main: 2
Thread4: 7
Thread2: 7
Thread3: 2
Thread2: 8
Thread4: 8
main: 3
Thread3: 3
Thread4: 9
main: 4
Thread2: 9
Thread3: 4
Thread4: 10
Thread2: 10
Thread3: 5
main: 5
Thread4: 11
Thread2: 11
main: 6
Thread3: 6
Thread4: 12
Thread3: 7
Thread2: 12
main: 7
Thread4: 13
Thread3: 8
main: 8
Thread2: 13
Thread3: 9
Thread2: 14
Thread4: 14
main: 9
Thread3: 10
Thread4: 15
main: 10
Thread2: 15
Thread3: 11
Thread4: 16
Thread2: 16
main: 11
Thread4: 17
main: 12
Thread2: 17
Thread3: 12
Thread4: 18
Thread2: 18
main: 13
Thread3: 13
Thread4: 19
main: 14
Thread2: 19
Thread3: 14
Thread4: 20
main: 15
Thread2: 20
Thread3: 15
Thread2: 21
main: 16
Thread4: 21
Thread3: 16
Thread2: 22
main: 17
Thread3: 17
Thread4: 22
Thread2: 23
main: 18
Thread4: 23
Thread3: 18
Thread2: 24
main: 19
Thread4: 24
Thread3: 19
Thread2: 25
main: 20
Thread3: 20
Thread4: 25
Thread2: 26
main: 21
Thread4: 26
Thread3: 21
Thread2: 27
main: 22
Thread4: 27
Thread3: 22
main: 23
Thread2: 28
Thread4: 28
Thread3: 23
main: 24
Thread2: 29
Thread4: 29
Thread3: 24
main: 25
Thread2: 30
Thread4: 30
Thread3: 25
Thread2: 31
main: 26
Thread4: 31
Thread3: 26
Thread2: 32
main: 27
Thread4: 32
Thread3: 27
Thread2: 33
main: 28
Thread4: 33
Thread3: 28
Thread2: 34
main: 29
Thread4: 34
Thread3: 29
Thread2: 35
Thread4: 35
main: 30
Thread3: 30
Thread2: 36
Thread4: 36
main: 31
Thread3: 31
main: 32
Thread2: 37
Thread4: 37
Thread3: 32
main: 33
Thread2: 38
Thread4: 38
Thread3: 33
Thread2: 39
main: 34
Thread4: 39
Thread3: 34
main: 35
Thread2: 40
Thread4: 40
Thread3: 35
main: 36
Thread2: 41
Thread4: 41
Thread3: 36
Thread2: 42
main: 37
Thread4: 42
Thread3: 37
Thread2: 43
main: 38
Thread4: 43
Thread3: 38
Thread2: 44
main: 39
Thread4: 44
Thread3: 39
Thread2: 45
main: 40
Thread4: 45
Thread3: 40
main: 41
Thread2: 46
Thread4: 46
Thread3: 41
main: 42
Thread4: 47
Thread2: 47
Thread3: 42
main: 43
Thread4: 48
Thread2: 48
Thread3: 43
main: 44
Thread2: 49
Thread4: 49
Thread3: 44
main: 45
Thread2: 50
Thread4: 50
Thread3: 45
main: 46
Thread2: 51
Thread4: 51
Thread3: 46
main: 47
Thread2: 52
Thread4: 52
main: 48
Thread4: 53
Thread2: 53
Thread3: 47
Thread4: 54
Thread3: 48
main: 49
Thread2: 54
Thread3: 49
Thread2: 55
main: 50
Thread4: 55
Thread2: 56
Thread3: 50
main: 51
Thread4: 56
Thread2: 57
main: 52
Thread4: 57
Thread3: 51
Thread3: 52
main: 53
Thread4: 58
Thread2: 58
main: 54
Thread2: 59
Thread4: 59
Thread3: 53
main: 55
Thread2: 60
Thread3: 54
Thread4: 60
Thread4: 61
Thread2: 61
main: 56
Thread3: 55
main: 57
Thread2: 62
Thread4: 62
Thread3: 56
Thread4: 63
main: 58
Thread2: 63
Thread3: 57
main: 59
Thread4: 64
Thread2: 64
Thread3: 58
main: 60
Thread4: 65
Thread2: 65
Thread3: 59
main: 61
Thread4: 66
Thread2: 66
Thread3: 60
main: 62
Thread4: 67
Thread2: 67
Thread3: 61
main: 63
Thread4: 68
Thread2: 68
Thread3: 62
main: 64
Thread4: 69
Thread2: 69
Thread3: 63
Thread4: 70
main: 65
Thread2: 70
Thread3: 64
main: 66
Thread4: 71
Thread2: 71
Thread3: 65
main: 67
Thread4: 72
Thread2: 72
Thread3: 66
Thread4: 73
main: 68
Thread2: 73
Thread3: 67
Thread4: 74
main: 69
Thread2: 74
Thread3: 68
main: 70
Thread4: 75
Thread3: 69
Thread2: 75
Thread4: 76
main: 71
Thread3: 70
Thread2: 76
Thread4: 77
main: 72
Thread3: 71
Thread2: 77
main: 73
Thread4: 78
Thread2: 78
Thread3: 72
main: 74
Thread4: 79
Thread2: 79
Thread3: 73
Thread4: 80
main: 75
Thread2: 80
Thread3: 74
main: 76
Thread4: 81
Thread2: 81
Thread3: 75
main: 77
Thread4: 82
Thread2: 82
Thread3: 76
main: 78
Thread4: 83
Thread2: 83
Thread3: 77
Thread4: 84
main: 79
Thread2: 84
Thread3: 78
Thread4: 85
main: 80
Thread2: 85
Thread3: 79
Thread4: 86
main: 81
Thread2: 86
Thread3: 80
Thread4: 87
main: 82
Thread2: 87
Thread3: 81
Thread4: 88
main: 83
Thread2: 88
Thread3: 82
Thread4: 89
main: 84
Thread2: 89
Thread3: 83
main: 85
Thread4: 90
Thread2: 90
Thread3: 84
Thread4: 91
main: 86
Thread2: 91
Thread3: 85
Thread4: 92
main: 87
Thread2: 92
Thread3: 86
Thread4: 93
main: 88
Thread2: 93
Thread3: 87
Thread4: 94
main: 89
Thread2: 94
Thread4: 95
Thread3: 88
main: 90
Thread2: 95
Thread4: 96
main: 91
Thread3: 89
Thread2: 96
Thread4: 97
main: 92
Thread3: 90
Thread4: 98
Thread2: 97
main: 93
Thread3: 91
Thread2: 98
Thread4: 99
main: 94
Thread3: 92
Thread2: 99
main: 95
Thread3: 93
main: 96
Thread3: 94
main: 97
Thread3: 95
main: 98
Thread3: 96
main: 99
Thread3: 97
Thread3: 98
Thread3: 99
view join result
  • setDaemon方法

   将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出该方法必须在启动线程前调用。当传入参数为true时则表示设置该线程为守护线程。守护线程的应用场景多用在后台管理,业务逻辑不应该封装在守护线程中,不然结果不稳定。

  • interrupt方法

   该方法的作用在于中断线程,线程中断后,将不在执行。

  • yield()方法

  yield方法是一种礼让方法,意思就是让出cpu执行权给其他线程,例如下属代码最后的效果就是Thread1和Thread2交替执行,因为双方都在执行一次后让出执行权。

public class ThreadTest {
    public static void main(String args[]) {
        MyRunnable myRunnable = new MyRunnable();

        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);

        t1.setName("Thread1");
        t2.setName("Thread2");

        t1.start();
        t2.start();


    }

}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.yield();
        }
    }

}
View Code

 

线程安全问题

  多线程的好处在于效率高,但是很多人都怕用多线程,因为觉得多线程会有安全问题,这里要说的是多线程并不一定是不安全的,如果有数据共享,才会存在安全问题,如果完全不存在数据共享,相互独立的执行,这是不会存在安全问题的。那在有数据共享的情况下(Runnable实现情况下一般都是有数据共享的),如何保证线程执行的安全性呢?

  用synchronized同步控制来保证安全。synchronized可以用来修饰代码块和方法,synchronized同步控制相当于对框起来的代码加了一把锁,每个线程只有拿到这把锁才能进入使用操作代码。

多线程实现购票用例

public class ThreadTest {
    public static void main(String args[]) {
        MyRunnable myRunnable = new MyRunnable(40);

        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.setName("售票口1");
        t2.setName("售票口2");
        t3.setName("售票口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunnable implements Runnable {
    private int tickets;

    public MyRunnable(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        sellTickets();
//        while (true) {
//            synchronized (this) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                if (tickets > 0) {
//                    System.out.println(Thread.currentThread().getName() + "正在出售第: " + tickets-- + " 张票");
//                }
//            }
//        }
    }

    public synchronized void sellTickets(){
        while (true) {
            synchronized (this) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第: " + tickets-- + " 张票");
                }
            }
        }
    }

}
View Code

 

JDK5新特性Lock

利用synchronized对于在哪里开始加锁了哪里释放了锁不够明显,JDK5提供的新特性Lock可以明确加锁和释放锁的位置。如下代码测试:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {
    public static void main(String args[]) {
        MyRunnable myRunnable = new MyRunnable(40);

        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.setName("售票口1");
        t2.setName("售票口2");
        t3.setName("售票口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunnable implements Runnable {
    private int tickets;
    private Lock lock = new ReentrantLock();

    public MyRunnable(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第: " + tickets-- + " 张票");
                }
            } finally {
                lock.unlock();//为了防止代码出错锁不能解除的情况
            }
            
        }
    }
}
View Code

 

posted @ 2015-12-07 21:22  FightingBoy&Gril  阅读(334)  评论(0编辑  收藏  举报