【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
从结果看出,这是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); } } }
运行结果则如下:
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
- 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(); } } } }
为了获得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
- 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(); } } }
线程安全问题
多线程的好处在于效率高,但是很多人都怕用多线程,因为觉得多线程会有安全问题,这里要说的是多线程并不一定是不安全的,如果有数据共享,才会存在安全问题,如果完全不存在数据共享,相互独立的执行,这是不会存在安全问题的。那在有数据共享的情况下(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-- + " 张票"); } } } } }
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();//为了防止代码出错锁不能解除的情况 } } } }