线程的生命周期
在Java中,JVM线程可以有如下 6 种状态(仅JAVA内的状态,不是操作系统OS的状态) :
New (新创建)
Runnable (可运行)
Blocked (被阻塞)
Waiting (等待中)
Timed waiting (计时等待)
Terminated (被终止)
New新创建
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
Runnable可运行
在Java中,可运行态包括:Ready 和 Running
Ready就绪态
该状态下的线程已经获得执行所需的所有资源,CPU只要分配执行权就能运行。
所有就绪态的线程存放在就绪队列中。
Running运行中
已获得CPU执行权,正在被CPU执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
可以通过yield放弃CPU执行(例如在非抢占式操作系统中,需要由正在执行的线程主动放弃CPU)
Blocked被阻塞
注意是被动语态,当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
而在Java中,阻塞态专指请求锁失败时而被迫进入的状态。 (通常:锁、IO、Socket等都资源。但在这里仅涉及锁)
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入Runnable-Ready就绪队列,等待执行。
Waiting等待中
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态表示它需要等待其他线程的指示才能继续运行。需要由其他线程唤醒。
进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
Timed wating计时等待
当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
它和等待态一样,并不是因为请求不到资源,而是主动进入。
进入该状态后释放CPU执行权 和 占有的资源。
与等待态的区别:到了设定时间后可自动进入Runnable-Ready
Terminated 被终止
包括运行结束自然终止,或者没有捕获异常而终止。
另外:
interrupt中断是一种机制,与这里的线程状态是不同层次的概念。
当对一个线程调用 interrupt 方法时,线程的中断状态将被置位。这是每一个线程都具有的 boolean 标志。每个线程都应该不时地检査这个标志(比如while循环), 以判断线程是否被中断。 但是, 如果线程被阻塞, 就无法检测中断状态。这是产生 InterruptedExceptioii 异常的地方。当在一个被阻塞的线程(调用 sleep 或 wait) 上调用 interrupt 方法时,阻塞调用将会被Interrupted Exception 异常中断。
线程的同步:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式 * * 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题 * 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。 * 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他 * 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。 * * * 4.在Java中,我们通过同步机制,来解决线程的安全问题。 * * 方式一:同步代码块 * * synchronized(同步监视器){ * //需要被同步的代码 * * } * 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。 * 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。 * 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 * 要求:多个线程必须要共用同一把锁。 * * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。 * 方式二:同步方法。 * 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。 * * * 5.同步的方式,解决了线程的安全问题。---好处 * 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性
|
方式一:
使用同步代码块解决接口的方式的线程安全问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class Window1 implements Runnable{
private int ticket = 100;
@Override public void run() {
while(true){ synchronized (this){
if (ticket > 0) {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--; } else { break; } } } } }
public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1();
Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); }
}
class Dog{
}
|
使用同步代码块解决继承Thread类的方式的线程安全问题
1 2 3 4 5
| 使用同步代码块解决继承Thread类的方式的线程安全问题 * * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式 * * 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class Window2 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override public void run() {
while(true){
synchronized (Window2.class){
if(ticket > 0){
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(getName() + ":卖票,票号为:" + ticket); ticket--; }else{ break; } }
}
} }
public class WindowTest2 { public static void main(String[] args) { Window2 t1 = new Window2(); Window2 t2 = new Window2(); Window2 t3 = new Window2();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start();
} }
|
方式二:
使用同步方法解决实现Runnable接口的线程安全问题
1 2 3 4 5 6 7
| 使用同步方法解决实现Runnable接口的线程安全问题 * * * 关于同步方法的总结: * 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。 * 2. 非静态的同步方法,同步监视器是:this * 静态的同步方法,同步监视器是:当前类本身
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Window3 implements Runnable {
private int ticket = 100;
@Override public void run() { while (true) {
show(); } }
private synchronized void show(){
if (ticket > 0) {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--; } } }
public class WindowTest3 { public static void main(String[] args) { Window3 w = new Window3();
Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); }
}
|
使用同步方法处理继承Thread类的方式中的线程安全问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Window4 extends Thread {
private static int ticket = 100;
@Override public void run() {
while (true) {
show(); }
} private static synchronized void show(){ if (ticket > 0) {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } } }
public class WindowTest4 { public static void main(String[] args) { Window4 t1 = new Window4(); Window4 t2 = new Window4(); Window4 t3 = new Window4();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start();
} }
|
线程安全的单例模式之懒汉式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){
synchronized (Bank.class) { if(instance == null){
instance = new Bank(); }
} } return instance; }
}
|
线程的死锁问题:
死锁的演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| class A { public synchronized void foo(B b) { System.out.println("当前线程名: " + Thread.currentThread().getName() + " 进入了A实例的foo方法");
System.out.println("当前线程名: " + Thread.currentThread().getName() + " 企图调用B实例的last方法"); b.last(); }
public synchronized void last() { System.out.println("进入了A类的last方法内部"); } }
class B { public synchronized void bar(A a) { System.out.println("当前线程名: " + Thread.currentThread().getName() + " 进入了B实例的bar方法");
System.out.println("当前线程名: " + Thread.currentThread().getName() + " 企图调用A实例的last方法"); a.last(); }
public synchronized void last() { System.out.println("进入了B类的last方法内部"); } }
public class DeadLock implements Runnable { A a = new A(); B b = new B();
public void init() { Thread.currentThread().setName("主线程"); a.foo(b); System.out.println("进入了主线程之后"); }
public void run() { Thread.currentThread().setName("副线程"); b.bar(a); System.out.println("进入了副线程之后"); }
public static void main(String[] args) { DeadLock dl = new DeadLock(); new Thread(dl).start();
dl.init(); } }
|
解决线程安全问题的方式三:Lock锁 —- JDK5.0新增
1 2 3 4 5 6 7 8 9 10 11 12
| 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增 * * 1. 面试题:synchronized 与 Lock的异同? * 相同:二者都可以解决线程安全问题 * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) * * 2.优先使用顺序: * Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外) * * * 面试题:如何解决线程安全问题?有几种方式
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class Window implements Runnable{
private int ticket = 100; private ReentrantLock lock = new ReentrantLock();
@Override public void run() { while(true){ try{
lock.lock();
if(ticket > 0){
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; }else{ break; } }finally { lock.unlock(); }
} } }
public class LockTest { public static void main(String[] args) { Window w = new Window();
Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
- lock锁如果有好几个对象,那就得加上static保证lock唯一。
练习:
1 2 3 4 5 6 7 8
| 银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
分析: 1.是否是多线程问题? 是,两个储户线程 2.是否有共享数据? 有,账户(或账户余额) 3.是否有线程安全问题?有 4.需要考虑如何解决线程安全问题?同步机制:有三种方式。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Account{ private double balance;
public Account(double balance) { this.balance = balance; }
public synchronized void deposit(double amt){ if(amt > 0){ balance += amt;
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + ":存钱成功。余额为:" + balance); } } }
class Customer extends Thread{
private Account acct;
public Customer(Account acct) { this.acct = acct; }
@Override public void run() {
for (int i = 0; i < 3; i++) { acct.deposit(1000); }
} }
public class AccountTest {
public static void main(String[] args) { Account acct = new Account(0); Customer c1 = new Customer(acct); Customer c2 = new Customer(acct);
c1.setName("甲"); c2.setName("乙");
c1.start(); c2.start(); } }
|