线程的生命周期
JDK中Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历一下五种状态。
1、新建:当一个Thread类或其子类的对象被声明并创建时,新生的程序对象处于新建状态。
2、就绪:处于新建状态的线程被start()后,将进入线程队列的等待CPU时间片,此时它具备了运行的条件,知识没有分配到CPU资源。
3、运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()定义了线程的操作和功能。
4、阻塞:在某种特殊情况下,被认为挂起或执行输入输出操作时,让处CPU并临时终止自己的执行,进入阻塞状态。
5、死亡:线程完成了它的全部工作或者线程被提前强制性地终止或者出现异常导致结束。
线程的安全问题
以窗口卖票为例:三个窗口同时卖100票
/**
runnable方式实现多窗口卖票
*/
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread t1 = new Thread(window1);
Thread t2 = new Thread(window1);
Thread t3 = new Thread(window1);
t1.start();
t2.start();
t3.start();
}
}
执行结果会出现重票,错票现象,这就是线程安全问题
Thread-0:卖票,票号为:100
Thread-1:卖票,票号为:100
Thread-2:卖票,票号为:100
Thread-2:卖票,票号为:97
Thread-2:卖票,票号为:96
Thread-2:卖票,票号为:95
问题的出现原因:
当某个线程操作车票的过程中,尚未操作完成,其他的线程参与进来,也操作车票。
如何解决:
当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以开始操作ticket。
同步机制解决线程安全问题
同步的方式,解决了线程的安全问题。
操作同步代码时,只能有一个线程参与,其他线程等待。相当于时一个单线程的过程,效率低。
方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码
}
1、操作共享数据的代码,既需要被同步的代码, 同步的代码不能多了也不能少了。
2、共享数据:多个线程共同操作的变量,例子中的ticket
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要公用同一把锁,拥有唯一性。
Runnable创建线程的同步代码块代码
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
synchronized (this) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread t1 = new Thread(window1);
Thread t2 = new Thread(window1);
Thread t3 = new Thread(window1);
t1.start();
t2.start();
t3.start();
}
}
继承Thread方式的同步代码块代码
class Window extends Thread{
//多线程共享静态变量
private static int ticket = 100;
@Override
public void run() {
while (true){
synchronized (Window.class) {
if (ticket > 0){
System.out.println(getName() + ":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
//创建了三个对象
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
说明:
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。this指代当前对象。
在继承Thread接口创建的多线程方式中,不能够时用this来充当锁。原因:失去了唯一性,this指代了多了个对象。我们可以用 类名.class 来充当锁对象。在面向对象中,类也是一个对象。
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步。
1、同步方法仍然涉及到同步监视器,知识不需要显式声明。
2、非静态的同步方法,同步监视器:this
3、静态同步方法,同步监视器:当前类本身
runnable方式
/**
runnable方式实现多窗口卖票
*/
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public synchronized void show(){//在同步方法中锁是this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread t1 = new Thread(window1);
Thread t2 = new Thread(window1);
Thread t3 = new Thread(window1);
t1.start();
t2.start();
t3.start();
}
}
继承Thread方式
/*
继承Thread方式
*/
class Window extends Thread{
//多线程共享静态变量
private static int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public static synchronized void show(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:"+ticket);
ticket--;
}
}
}
public class WindowTest {
public static void main(String[] args) {
//创建了三个对象
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}