2.2 Java线程的生命周期
线程状态,也称为线程的生命周期,代表着线程再代码运行中的状态
Thread.State
2.2.1 六种状态
- NEW:刚刚新建的Thread的对象,但还未调用start()启动线程时,
- RUNNABLE: 线程对象创建后,其他线程(比如 main 线程)调用了该对象的
start
方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。 - BLOCKED: 线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。
sleep
,suspend
,wait
等方法都可以导致线程阻塞 - WAITING:等待另外一个线程执行特定的动作((通知或中断))
- TIMED_WAITING:该状态不同于WAITING,它可以在指定的时间后自行返回。
- TERMINATED:表示该线程已经执行完毕,如果一个线程的
run
方法执行结束或者调用stop
方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start
方法令其进入就绪。
2.3 线程的同步
2.3.1 线程的安全问题
public class TicketMain {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
Thread thread3 = new Thread(t1);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
public class Ticket implements Runnable{
private int ticketCount = 100;
@Override
public void run() {
while (true){
if (ticketCount == 0){
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
ticketCount--;
}
}
}
}
安全问题:
- 相同得票出现了多次
- 出现了负数票
注:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
共享数据:多个线程共同操作的变量:比如上面代码的票数 ticketCount
2.3.2 同步代码块
锁多条语句操作共享数据,可以实现同步代码块实现
synchronized(同步监视器){
多条语句操作共享数据的代码
}
注:
-
默认情况是打开的,只要一个线程进去执行代码了,锁就会关闭。当线程执行完毕出来,锁才会自动打开
-
同步监视器(锁)可以是任何对象。因为任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。但是要求多个线程要共用同一把锁
-
Runnable可以考虑用this当作同步监视器
-
一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)
同步监视器的执行过程:
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,锁定同步加速器并访问
好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很大时,因为每个线程都会去判断同步上的锁吗,这是很耗费资源的,无形中会降低程序的运行效率
public class TicketMain {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
Thread thread3 = new Thread(t1);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
public class Ticket implements Runnable{
private int ticketCount = 100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){ // 多个线程必须使用同一把锁
if (ticketCount == 0){
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
ticketCount--;
}
}
}
}
}
2.3.3 同步方法
把synchronized关键字加到方法是,同步方法的锁对象是this
修饰符 synchronized 返回值类型 方法名(方法参数){}
同步代码块和同步方法的区别
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
- 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this)
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while (true){
synchronizedMethod();
if(ticketCount == 0){
break;
}
}
}
private synchronized void synchronizedMethod() {
if (ticketCount > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
ticketCount--;
}
}
}
public class Demo {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
class MyThread extends Thread {
private static int ticketCount = 100;
@Override
public void run() {
while (true){
synchronizedMethod();
if(ticketCount == 0){
break;
}
}
}
// 继承Thread 用静态方法
private static synchronized void synchronizedMethod() {
if (ticketCount > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
}
}
}
2.3.4 死锁
线程的死锁是指由于俩个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前进执行,**主要是因为锁的嵌套 **
产生死锁的必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保存条件:一个进程因请求资源而阻塞时,对已获得得资源保持不放
- 不剥夺条件:进程以获得得资源吗,在未使用之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接循环等待资源关系
只有破坏上面任意一个条件就可以避免死锁得发送
2.3.5 Lock(锁)
JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
- private final ReentrantLock lock = new ReentrantLock():可以传入参数 boolean ,如果为true 则按线程进入的顺序,如果false则随机抽取线程进入
package lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums = 100;
// 定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticketNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 解锁
lock.unlock();
}
}
}
}
注意:如果同步代码有异常,要将unlock()写入finally语句块,确保锁会得到释放
2.3.5.1 synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)