一.线程的生命周期及五种基本状态
由上图可知,线程具有五种基本状态
1.新建状态(New):当线程对象被创建后,即进入了新建状态,如:Thread t = new MyThread()。
2.就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。
3.运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
4.阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。
2.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二.多线程实现
1.继承Thread类 实现多线程
Thread类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
Log.d("TAG","Thread类实现多线程----Name----:"+Thread.currentThread().getName());
}
}
调用
MyThread t1 = new MyThread();//线程1 新建状态
t1.setName("Thread1");
MyThread t2 = new MyThread();//线程2 新建状态
t2.setName("Thread2");
MyThread t3 = new MyThread();//线程3 新建状态
t3.setName("Thread3");
t1.start();//线程1 就绪状态
t2.start();//线程2 就绪状态
t3.start();//线程3 就绪状态
结果1
D/TAG: Thread类实现多线程----Name----:Thread2
D/TAG: Thread类实现多线程----Name----:Thread3
D/TAG: Thread类实现多线程----Name----:Thread1
结果2
D/TAG: Thread类实现多线程----Name----:Thread1
D/TAG: Thread类实现多线程----Name----:Thread2
D/TAG: Thread类实现多线程----Name----:Thread3
结果3
D/TAG: Thread类实现多线程----Name----:Thread1
D/TAG: Thread类实现多线程----Name----:Thread2
D/TAG: Thread类实现多线程----Name----:Thread3
这样的结果也可以看出,虽然 创建线程(线程处于新建状态)和开始线程(线程处于就绪状态)是按照Thread1,Thread2,Thread3。顺序执行的。但是线程真正运行的顺序(线程处于运行状态)是不固定的。因为线程的真正运行是要看CPU调度的。
2.实现Runnable接口 实现多线程
Runnable实现类
public class MyRunnable implements Runnable {
@Override
public void run() {
Log.d("TAG","Runnable接口实现多线程----Name----:"+Thread.currentThread().getName());
}
}
调用
MyRunnable r = new MyRunnable();
Thread r1 = new Thread(r,"Runnable1");//线程1 新建状态
Thread r2 = new Thread(r,"Runnable2");//线程2 新建状态
Thread r3 = new Thread(r,"Runnable3");//线程3 新建状态
r1.start();//线程1 就绪状态
r2.start();//线程2 就绪状态
r3.start();//线程3 就绪状态
结果1
D/TAG: Runnable接口实现多线程----Name----:Runnable1
D/TAG: Runnable接口实现多线程----Name----:Runnable2
D/TAG: Runnable接口实现多线程----Name----:Runnable3
结果2
D/TAG: Runnable接口实现多线程----Name----:Runnable3
D/TAG: Runnable接口实现多线程----Name----:Runnable1
D/TAG: Runnable接口实现多线程----Name----:Runnable2
结果3
D/TAG: Runnable接口实现多线程----Name----:Runnable1
D/TAG: Runnable接口实现多线程----Name----:Runnable3
D/TAG: Runnable接口实现多线程----Name----:Runnable2
由上可知,无论是Thread类还是Runnable接口实现多线程。新线程进入就绪状态顺序如上 可是运行顺序结果却不一定一样,这是因为进入就绪状态的线程要等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。并且CPU调度有不确定性 所以线程执行顺序就有区别了。
三.多线程关键知识讲解之synchronized
以买票Demo为例
线程类
public class MyRunnable implements Runnable {
private int ticket;//总票数
public MyRunnable(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
for (int i = 0; i <= ticket; i++) {//举例 比总票数大即可
if (ticket > 0) {
Log.d("TAG", Thread.currentThread().getName() + "----正在卖票----票总数剩余:" + ticket);
ticket--;
}
}
}
}
调用
MyRunnable r = new MyRunnable(30);
Thread r1 = new Thread(r, "窗口1");
Thread r2 = new Thread(r, "窗口2");
Thread r3 = new Thread(r, "窗口3");
Thread r4 = new Thread(r, "窗口4");
Thread r5 = new Thread(r, "窗口5");
r1.start();//窗口1 就绪状态
r2.start();//窗口2 就绪状态
r3.start();//窗口3 就绪状态
r4.start();//窗口4 就绪状态
r5.start();//窗口5 就绪状态
结果
D/TAG: 窗口1----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:28
D/TAG: 窗口1----正在卖票----票总数剩余:28
D/TAG: 窗口2----正在卖票----票总数剩余:26
D/TAG: 窗口1----正在卖票----票总数剩余:26
D/TAG: 窗口2----正在卖票----票总数剩余:24
D/TAG: 窗口1----正在卖票----票总数剩余:24
D/TAG: 窗口2----正在卖票----票总数剩余:22
D/TAG: 窗口1----正在卖票----票总数剩余:22
D/TAG: 窗口2----正在卖票----票总数剩余:20
D/TAG: 窗口1----正在卖票----票总数剩余:20
D/TAG: 窗口2----正在卖票----票总数剩余:18
D/TAG: 窗口1----正在卖票----票总数剩余:18
D/TAG: 窗口2----正在卖票----票总数剩余:16
D/TAG: 窗口1----正在卖票----票总数剩余:16
D/TAG: 窗口2----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:14
D/TAG: 窗口2----正在卖票----票总数剩余:12
D/TAG: 窗口1----正在卖票----票总数剩余:12
D/TAG: 窗口2----正在卖票----票总数剩余:10
D/TAG: 窗口1----正在卖票----票总数剩余:10
D/TAG: 窗口4----正在卖票----票总数剩余:8
D/TAG: 窗口4----正在卖票----票总数剩余:7
D/TAG: 窗口4----正在卖票----票总数剩余:6
D/TAG: 窗口4----正在卖票----票总数剩余:5
D/TAG: 窗口5----正在卖票----票总数剩余:7
D/TAG: 窗口4----正在卖票----票总数剩余:4
D/TAG: 窗口5----正在卖票----票总数剩余:2
D/TAG: 窗口3----正在卖票----票总数剩余:1
D/TAG: 窗口2----正在卖票----票总数剩余:30
D/TAG: 窗口1----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:28
D/TAG: 窗口1----正在卖票----票总数剩余:28
D/TAG: 窗口2----正在卖票----票总数剩余:26
D/TAG: 窗口1----正在卖票----票总数剩余:26
D/TAG: 窗口2----正在卖票----票总数剩余:24
D/TAG: 窗口1----正在卖票----票总数剩余:24
D/TAG: 窗口2----正在卖票----票总数剩余:22
D/TAG: 窗口1----正在卖票----票总数剩余:22
D/TAG: 窗口2----正在卖票----票总数剩余:20
D/TAG: 窗口1----正在卖票----票总数剩余:20
D/TAG: 窗口2----正在卖票----票总数剩余:18
D/TAG: 窗口1----正在卖票----票总数剩余:18
D/TAG: 窗口2----正在卖票----票总数剩余:16
D/TAG: 窗口1----正在卖票----票总数剩余:16
D/TAG: 窗口2----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:12
D/TAG: 窗口2----正在卖票----票总数剩余:12
D/TAG: 窗口1----正在卖票----票总数剩余:10
D/TAG: 窗口2----正在卖票----票总数剩余:10
D/TAG: 窗口4----正在卖票----票总数剩余:8
D/TAG: 窗口4----正在卖票----票总数剩余:7
D/TAG: 窗口4----正在卖票----票总数剩余:6
D/TAG: 窗口4----正在卖票----票总数剩余:5
D/TAG: 窗口4----正在卖票----票总数剩余:4
D/TAG: 窗口3----正在卖票----票总数剩余:3
D/TAG: 窗口3----正在卖票----票总数剩余:2
D/TAG: 窗口5----正在卖票----票总数剩余:1
D/TAG: 窗口1----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:28
D/TAG: 窗口1----正在卖票----票总数剩余:28
D/TAG: 窗口2----正在卖票----票总数剩余:26
D/TAG: 窗口1----正在卖票----票总数剩余:26
D/TAG: 窗口2----正在卖票----票总数剩余:24
D/TAG: 窗口1----正在卖票----票总数剩余:24
D/TAG: 窗口2----正在卖票----票总数剩余:23
D/TAG: 窗口1----正在卖票----票总数剩余:21
D/TAG: 窗口2----正在卖票----票总数剩余:21
D/TAG: 窗口2----正在卖票----票总数剩余:19
D/TAG: 窗口1----正在卖票----票总数剩余:19
D/TAG: 窗口2----正在卖票----票总数剩余:18
D/TAG: 窗口1----正在卖票----票总数剩余:17
D/TAG: 窗口2----正在卖票----票总数剩余:16
D/TAG: 窗口2----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:14
D/TAG: 窗口2----正在卖票----票总数剩余:12
D/TAG: 窗口1----正在卖票----票总数剩余:12
D/TAG: 窗口2----正在卖票----票总数剩余:11
D/TAG: 窗口1----正在卖票----票总数剩余:9
D/TAG: 窗口4----正在卖票----票总数剩余:8
D/TAG: 窗口4----正在卖票----票总数剩余:7
D/TAG: 窗口4----正在卖票----票总数剩余:6
D/TAG: 窗口4----正在卖票----票总数剩余:5
D/TAG: 窗口4----正在卖票----票总数剩余:4
D/TAG: 窗口3----正在卖票----票总数剩余:4
D/TAG: 窗口3----正在卖票----票总数剩余:2
D/TAG: 窗口5----正在卖票----票总数剩余:1
.....
五个窗口卖30张票,显然上面三个结果都不对。
改进
public class MyRunnable implements Runnable {
private int ticket;//总票数
public MyRunnable(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
synchronized (this) {
for (int i = 0; i <= ticket; i++) {//举例 比总票数大即可
if (ticket > 0) {
Log.d("TAG", Thread.currentThread().getName() + "----正在卖票----票总数剩余:" + ticket);
ticket--;
}
}
}
}
}
即给多线程要操作的代码块(买票)进行同步
结果
D/TAG: 窗口3----正在卖票----票总数剩余:30
D/TAG: 窗口3----正在卖票----票总数剩余:29
D/TAG: 窗口3----正在卖票----票总数剩余:28
D/TAG: 窗口3----正在卖票----票总数剩余:27
D/TAG: 窗口3----正在卖票----票总数剩余:26
D/TAG: 窗口3----正在卖票----票总数剩余:25
D/TAG: 窗口3----正在卖票----票总数剩余:24
D/TAG: 窗口3----正在卖票----票总数剩余:23
D/TAG: 窗口3----正在卖票----票总数剩余:22
D/TAG: 窗口3----正在卖票----票总数剩余:21
D/TAG: 窗口3----正在卖票----票总数剩余:20
D/TAG: 窗口3----正在卖票----票总数剩余:19
D/TAG: 窗口3----正在卖票----票总数剩余:18
D/TAG: 窗口3----正在卖票----票总数剩余:17
D/TAG: 窗口3----正在卖票----票总数剩余:16
D/TAG: 窗口3----正在卖票----票总数剩余:15
D/TAG: 窗口1----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:13
D/TAG: 窗口1----正在卖票----票总数剩余:12
D/TAG: 窗口1----正在卖票----票总数剩余:11
D/TAG: 窗口1----正在卖票----票总数剩余:10
D/TAG: 窗口1----正在卖票----票总数剩余:9
D/TAG: 窗口1----正在卖票----票总数剩余:8
D/TAG: 窗口1----正在卖票----票总数剩余:7
D/TAG: 窗口2----正在卖票----票总数剩余:6
D/TAG: 窗口2----正在卖票----票总数剩余:5
D/TAG: 窗口2----正在卖票----票总数剩余:4
D/TAG: 窗口2----正在卖票----票总数剩余:3
D/TAG: 窗口4----正在卖票----票总数剩余:2
D/TAG: 窗口4----正在卖票----票总数剩余:1
D/TAG: 窗口2----正在卖票----票总数剩余:30
D/TAG: 窗口2----正在卖票----票总数剩余:29
D/TAG: 窗口2----正在卖票----票总数剩余:28
D/TAG: 窗口2----正在卖票----票总数剩余:27
D/TAG: 窗口2----正在卖票----票总数剩余:26
D/TAG: 窗口2----正在卖票----票总数剩余:25
D/TAG: 窗口2----正在卖票----票总数剩余:24
D/TAG: 窗口2----正在卖票----票总数剩余:23
D/TAG: 窗口2----正在卖票----票总数剩余:22
D/TAG: 窗口2----正在卖票----票总数剩余:21
D/TAG: 窗口2----正在卖票----票总数剩余:20
D/TAG: 窗口2----正在卖票----票总数剩余:19
D/TAG: 窗口2----正在卖票----票总数剩余:18
D/TAG: 窗口2----正在卖票----票总数剩余:17
D/TAG: 窗口2----正在卖票----票总数剩余:16
D/TAG: 窗口2----正在卖票----票总数剩余:15
D/TAG: 窗口1----正在卖票----票总数剩余:14
D/TAG: 窗口1----正在卖票----票总数剩余:13
D/TAG: 窗口1----正在卖票----票总数剩余:12
D/TAG: 窗口1----正在卖票----票总数剩余:11
D/TAG: 窗口1----正在卖票----票总数剩余:10
D/TAG: 窗口1----正在卖票----票总数剩余:9
D/TAG: 窗口1----正在卖票----票总数剩余:8
D/TAG: 窗口1----正在卖票----票总数剩余:7
D/TAG: 窗口3----正在卖票----票总数剩余:6
D/TAG: 窗口3----正在卖票----票总数剩余:5
D/TAG: 窗口3----正在卖票----票总数剩余:4
D/TAG: 窗口3----正在卖票----票总数剩余:3
D/TAG: 窗口5----正在卖票----票总数剩余:2
D/TAG: 窗口5----正在卖票----票总数剩余:1
D/TAG: 窗口1----正在卖票----票总数剩余:30
D/TAG: 窗口1----正在卖票----票总数剩余:29
D/TAG: 窗口1----正在卖票----票总数剩余:28
D/TAG: 窗口1----正在卖票----票总数剩余:27
D/TAG: 窗口1----正在卖票----票总数剩余:26
D/TAG: 窗口1----正在卖票----票总数剩余:25
D/TAG: 窗口1----正在卖票----票总数剩余:24
D/TAG: 窗口1----正在卖票----票总数剩余:23
D/TAG: 窗口1----正在卖票----票总数剩余:22
D/TAG: 窗口1----正在卖票----票总数剩余:21
D/TAG: 窗口1----正在卖票----票总数剩余:20
D/TAG: 窗口1----正在卖票----票总数剩余:19
D/TAG: 窗口1----正在卖票----票总数剩余:18
D/TAG: 窗口1----正在卖票----票总数剩余:17
D/TAG: 窗口1----正在卖票----票总数剩余:16
D/TAG: 窗口1----正在卖票----票总数剩余:15
D/TAG: 窗口2----正在卖票----票总数剩余:14
D/TAG: 窗口2----正在卖票----票总数剩余:13
D/TAG: 窗口2----正在卖票----票总数剩余:12
D/TAG: 窗口2----正在卖票----票总数剩余:11
D/TAG: 窗口2----正在卖票----票总数剩余:10
D/TAG: 窗口2----正在卖票----票总数剩余:9
D/TAG: 窗口2----正在卖票----票总数剩余:8
D/TAG: 窗口2----正在卖票----票总数剩余:7
D/TAG: 窗口5----正在卖票----票总数剩余:6
D/TAG: 窗口5----正在卖票----票总数剩余:5
D/TAG: 窗口5----正在卖票----票总数剩余:4
D/TAG: 窗口5----正在卖票----票总数剩余:3
D/TAG: 窗口3----正在卖票----票总数剩余:2
D/TAG: 窗口3----正在卖票----票总数剩余:1
正确
由上述举例可知,当多个线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。
synchronized既可以同步方法也可以同步代码块。
同步方法
public synchronized void test() {
}
同步代码块
public void test() {
synchronized(obj) {
System.out.println("===");
}
int m=90;
int n=100;
...
}
synchronized 用在方法名上,当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,去执行方法test。这样显然有时会造成资源浪费和不必要的等待。
四.多线程关键知识讲解之Lock
1.ReentrantLock 类
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁
2.总结 Lock与synchronized 的区别
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
Lock 和 synchronized 有一点明显的区别 :lock 必须在 finally 块中释放。否则,如果受保护的代码抛出异常,锁就有可能永远得不到释放。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹。而使用同步synchronized JVM 将确保锁会获得自动释放。
五.多线程关键知识讲解之四个常用方法
四个常用方法:
wait();
notify();
notifyAll();
sleep(long millis);
说明:
前三个方法都是Object的方法,并不是线程的方法。而最后一个是线程Thread类的方法。
1.wait()方法
释放占有的对象锁,线程进入等待池,释放cpu。而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。
2.sleep()方法
线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,该线程重新获得cpu执行代码。
wait()和sleep()最大的不同在于都释放cpu。但是wait()会释放对象锁,而sleep()不会。
sleep()方法可以在任何地方使用。wait()方法则只能在同步方法或同步块中使用。
3.notify()方法
该方法会唤醒因为调用对象的wait()而等待的线程,其实就是对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是,wait()和notify()必须在synchronized代码块中调用。
4.notifyAll()方法
则是唤醒所有等待的线程。
六.多线程关键知识讲解之其他知识
1.线程常用方法
<1> 取得当前线程对象: currentThread()
<2> 判断线程是否启动: isAlive()
<3> 线程的强行运行: join()
<4> 线程的休眠: sleep()
<5> 线程的礼让: yield()
<6> 取得当前线程对象: currentThread()
<7> start:启动线程
<8> setPriority:设置线程优先级
<9> getPriority: 获得线程优先级
<10> 取得线程名称: getName()
<11> 设置线程名称: setName()
<12> 设置线程幽灵状态: setDaemon()
<13> 取得线程幽灵状态: isDaemon()
2.什么是线程
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。
3.线程和进程有什么区别
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。
4.Thread 类中的start() 和 run() 方法有什么区别
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
5.Java中Runnable和Callable有什么不同
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
5.Java中CyclicBarrier 和 CountDownLatch有什么不同
CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。