一、Java的多线程有三种实现方式。
1、继承创建.
a、定义子类,重写run方法
b、创建Thread子类的实例(即现成对象)
c、调用start() 方法启动现成
特征:不可以共享变量。
public class FirstThreadByExtends extends Thread {
private int i; public FirstThreadByExtends(){
super();
}
public FirstThreadByExtends(String name){
super(name);
}
public void run() {
for (; i < 100; i++) {
System.out.println(getName() + " " + i);
}
} public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println("当前线程的名字: " + Thread.currentThread().getName());
if (i == 20) {
new FirstThreadByExtends("线程" + i).start();
}
}
} }
Note: 每次都是new出来的对象,没有共享变量。
2、实现Runable接口.
a、定义Runnable接口实现类,重写run()方法
b、创建Runnable实现类实例,作为Thread实例的Target
c、调用start() 方法启动现成
特征:可以共享变量,因为可以用同一个Target创建多个线程。
public class SecondThreadByImplementsRunnable implements Runnable {
private int i; public void run() {
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程的名字a: " + Thread.currentThread().getName());
if (i == 5) {
SecondThreadByImplementsRunnable st = new SecondThreadByImplementsRunnable();
new Thread(st,"新线程1").start();
new Thread(st,"新线程2").start();
}
}
}
}
SecondThreadByImplementsRunnable
3、使用Callable和future创建线程
a、创建Callable实现类,实现call()方法
b、创建Callable实现类的实例,用FutureTask实现类来包装
c、使用FutureTask作为Thread的Target来创建对象
d、通过调用FT的get()方法来获取线程的返回值
特征:可以有返回值,可以抛出异常
public class ThirdThreadByImplementsCallable implements Callable<Long> {
public Long call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThirdThreadByImplementsCallable tt = new ThirdThreadByImplementsCallable();
FutureTask<Long> task = new FutureTask<Long>(tt);
for (int i = 0; i < 100; i++) {
System.out.println("当前线程的名字: " + Thread.currentThread().getName() +" "+ i);
if (i == 5) {
new Thread(task,"有返回值的线程1").start();
}
if (i == 6) {
System.out.println("----------------------test---------------------------------");
new Thread(task,"有返回值的线程2").start();
}
}
System.out.println("子线程的返回值:" + task.get());
}
}
Note: 第二个线程实例没有跑起来,情况未知,待解决!
三种方式的对比
采用实现Ruannable或Callable接口的方式创建:
a、线程类只是实现了Runnable或Callable接口,还可以继承其他类
b、多个线程可以共享一个Target对象,适合多个相同线程处理统一份资源,从而将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的思想
c、劣势是 编程较复杂,如需访问当前现成还要调用 Thread.CurrentThread()方法.
继承Thread类的方式创建多线程:
a、优势,编写简单,可直接调用this获得当前线程
b、劣势,因继承了Thread,无法再继承其它类
二、线程生命周期
线程的生命周期包括5个状态:新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)。
a、新建和就绪
新建:当使用了new关键字创建了一个线程,该线程就处于新建状态。
就绪:当线程调用了start()方法,该线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器。该状态表示可以运行,准备被JVM线程调度器调度。
注意:启动线程的方法是调用线程实例的start(),如果直接调用run()方法,系统会把线程对象,当成一个普通对象,run()方法也会被当成普通方法,而不是线程执行体。
package lifecycle; public class InvokeRun extends Thread {
private int i; public void run() {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
} public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
//直接调用线程对象的Run方法
//系统会把线程对象当作普通对象,把run方法当成普通方法,
//所以,一下两行代码不会启动两个线程,而是依次执行两个Run方法
new InvokeRun().run();
new InvokeRun().run();
}
}
}
}
InvokeRun
b、运行和阻塞
运行:就绪状态时,获得了CPU,该线程就处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;但当线程数大于CPU数,也会出现轮换。具体线程调度细节取决于底层平台所采用的策略。
运行==>阻塞:
a、线程调用sleep()方法主动放弃所占用的资源
b、线程调用了一个阻塞式IO,在该方法返回之前,该线程被阻塞
c、线程试图获得一个同步监视器,但该同步监视器被其它线程所持有。关于同步监视器的知识、后面将有更深入的介绍
d、线程在等待某个通知(notify)
e、程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
阻塞==>运行:
a、sleep()方法超过了指定的时间
b、线程调用的阻塞式IO已经返回
c、线程成功获得了试图获得的同步监视器
d、线程正在等待某个通知时,其它线程发出了一个通知
e、处于挂起状态的的线程被调用了resume()方法
c、线程死亡
a、run()或call方法执行完成,线程正常结束
b、线程抛出一个未捕获的Exception或Error
c、直接调用线程的Stop()方法结束线程,容易死锁,不推荐使用
注意:不要试图对已经死亡的线程调用start()方法,会抛出不合法线程状态异常
public class StartDead implements Callable<Long> {
private int i; public static void main(String[] args) throws InterruptedException, ExecutionException {
StartDead sd = new StartDead();
FutureTask<Long> ft = new FutureTask<Long>(sd);
Thread t1 = new Thread(ft, "马上会死的线程,哇哈哈哈哈");
//t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
//get方法会阻塞main方法
System.out.println(ft.get());
for (int i = 0; i < 300; i++) {
if (i == 20) {
System.out.println(t1.isAlive() + "Love is forever!");
}
}
if (!t1.isAlive()) {
t1.start();
}
} public Long call() throws Exception {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return 100L;
} }
StartDead
三、线程控制
1、join()线程
Thread提供了一个线程等待另一个线程完成的方法 join(),当在某个程序执行流中调用了其它线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程执行完成为止。
join()方法由使用线程的程序调用,将大问题划分成许多小问题,每一个小问题分配一个线程。当所有小问题都得到处理以后,再调用主线程进行进一步操作。
join()方法有三种重载形式:
join(),等待被join的线程执行完成。
join(long millis),等待被join的线程的时间最长为millis毫秒。如果在millis毫秒之内被join的线程还没有执行结束,则不再等待。
join(long millis,int nanos),比上面多了个微秒。
public class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
} public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + " " + i);
}
} public static void main(String[] args) throws InterruptedException {
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++) {
if (i == 20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
jt.join(0);
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
} }
JoinThread
2、后台线程
所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。
下面是一个后台线程的示例:
public class Daemon extends Thread {
public Daemon(){ }
public Daemon(String name){
super(name);
} public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println(this.getName() + " " + i);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws InterruptedException {
Thread t = new Daemon("守护线程");
t.setDaemon(true);
t.start();
for (int i = 0; i < 10; i++) {
sleep(200);
System.out.println(Thread.currentThread().getName() + " " + i);
}
//Program end here
//and Daemon is end follow it.
System.out.println("啊哦,守护线程应该死了了吧!");
}
}
Daemon
3、线程睡眠:Sleep()
让线程暂停,进入阻塞状态。它是Thread的静态方法。
public class SleepThread {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
Thread.sleep(300);
}
}
}
SleepThread
4、线程让步:yield()
让线程进入就绪状态,等待CPU重新调度。不过在此期间,只有 相同或更高优先级的进程能被调用,不然的话会继续执行。
5、线程优先级
范围 1 ~ 10,三个静态常量 MAX_PRIORITY:10, MAX_PRIORITY:1, MAX_PRIORITY:5.
t.setPriority(MAX_PRIORITY);
四、线程同步
1、线程安全
银行取钱问题。
a. 用户输入帐号、密码,系统验证是否通过
b. 用户输入取款金额
c. 系统判断账户余额是否大于取款金额
d. 如果余额大于取款金额,成功; 如果余额小于取款金额,取款失败。
以下是没有同步控制的代码,也许有时候会正确,但只要有一次错误,那整个代码就是错误的。
2、同步代码快
synchronized (obj) {}, obj就是同步监视器,线程执行之前必须获得同步监视器的锁定。
流程:加锁==>操作==>释放锁。
synchronized (account) {
// If balance > drawAmount, check out!
if (account.getBalance() > drawAmount) {
System.out.println(this.getName() + "取钱成功!吐出钞票:" + this.drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
account.setBalance(account.getBalance() - this.drawAmount);
System.out.println("\t余额为:" + account.getBalance());
} else {
System.out.println(getName() + "余额不足,取款失败!");
}
}
3、同步方法
用同步方法可以非常方便的实现线程安全类。任意线程都可以安全访问。但无法显示指定同步监视器,同步监视器就是本身。
代码如下:
public synchronized void draw(double drawAmount) {
// If balance > drawAmount, check out!
if (this.getBalance() > drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" +
drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - drawAmount);
System.out.println("\t余额为:" + this.getBalance());
} else {
System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
}
}
4、释放同步监视器的锁定
a.同步方法、同步代码快执行结束,当前线程释放同步监视器。
b.break、reutrn终止了该代码块,该方法的继续执行,当前线程会释放同步监视器。
c. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法的异常结束时,当前线程将会释放同步监视器。
d. 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。
以下不会释放:
a.sleep().yield();
b.suspend(). resume(); 容易弄成死锁,不推荐
5、同步锁
lock.lock(), lock.unlock();
lock.lock();
try {
// If balance > drawAmount, check out!
if (this.getBalance() > drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - drawAmount);
System.out.println("\t余额为:" + this.getBalance());
} else {
System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
}
} finally {
lock.unlock();
}
6、死锁
资源的相互依赖,你把我要用的给锁了,我把你要用的给锁了,我们各自拿着一块等待对方,谁都不肯释放。