【Java】多线程的创建和线程安全问题

线程的状态

准确来说,应该有五个状态。

被创建后通过start()进入到运行状态。执行完run()方法或者调用stop()方法,会从运行状态进入到消亡状态。而运行状态,又会随时因为CPU的切换,进入到临时阻塞状态,或者叫“挂起”。如果在运行时期,调用sleep()或者wait()方法,会使线程进入冻结状态。当休眠结束或者调用notify()方法,会让线程回到运行状态,或者是临时阻塞状态。

                                 


多线程的创建

有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。

继承Thread类

第一种方式继承Thread类,将需要多线程处理的业务放到Thread类的run方法中。直接创建继承后的类对象,使用start()方法,即可开启多线程。

class MultiThreadDemo extends Thread{
    private String name;
    MultiThreadDemo(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for(int x=0;x<100;x++){
            System.out.println("任务:"+name+"...执行第"+x+"次...Thread-name:"+Thread.currentThread().getName());
        }
    }
}

public class MulitThreadTest {
    public static void main(String[] args) {
        MultiThreadDemo mtd1=new MultiThreadDemo("gg");
        MultiThreadDemo mtd2=new MultiThreadDemo("哈哈");
        mtd1.start();
        mtd2.start();
    }
}

这种方式的弊端在于:①获得了Thread中不需要的方法。②仅仅因为需要开启多线程,就继承了Thread了,成为了Thread类体系,而这个类本来跟Thread类毫不相干。③一旦继承了Thread类,就不能继承其它类了。

实现Runnable接口

第二种方式实现Runnable接口,覆写run方法。在调用的时候,把实现了该接口的类作为参数,传到Thread类的构造函数中,最后调用Thread对象的start()方法即可。

class MultiThreadDemo implements Runnable{
    private String name;
    MultiThreadDemo(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for(int x=0;x<100;x++){
            System.out.println("任务:"+name+"...执行第"+x+"次...Thread-name:"+Thread.currentThread().getName());
        }
    }
}

public class MulitThreadTest {
    public static void main(String[] args) {
        MultiThreadDemo mtd1=new MultiThreadDemo("哈哈");
        MultiThreadDemo mtd2=new MultiThreadDemo("gg");
        Thread t1=new Thread(mtd1);
        Thread t2=new Thread(mtd2);
        t1.start();
        t2.start();
    }
}

这种方式的优点在于:①避免了单继承的局限。②直接将具体任务封装成了具体的线程对象。 


卖票示例

四个机器(线程)同时卖100张票。下面这种使用继承Thread的方式,创建四个Ticket对象,肯定是不行的,各卖各的。

Ticket ticket1=new Ticket();
Ticket ticket2=new Ticket();
Ticket ticket3=new Ticket();
Ticket ticket4=new Ticket();
ticket1.start();
ticket2.start();
ticket3.start();
ticket4.start();

票是公有的,要被所有线程共享,一种方式是将票数变成静态的。另外一种是实现Runnable接口,四个线程用同一个对象

class Ticket implements Runnable{
    private int num=100;
    @Override
    public void run() {
        while(true){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
            }
        }
    }
}

public class TicektSell {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        Thread t1=new Thread(ticket);
        Thread t2=new Thread(ticket);
        Thread t3=new Thread(ticket);
        Thread t4=new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

这样,似乎就实现了四个机器同时卖100张票。 


线程安全问题

上面的代码,如果在if判断里面加上一条线程睡眠,就会出现“线程安全”问题。

if(num>0){
    try {
        Thread.sleep(100);
        }catch (InterruptedException e){e.printStackTrace();}
    System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}

票卖到了0,还在继续卖,甚至出现了负数!

                                                                            

原因在于:假设此时票数=1,线程0进行if判断,为true,进入到if代码块。这个时候,突然CPU切换线程线程0被挂起。此时轮到线程1进行if判断,票数还是1,依然可以进入到if代码块。这个时候,CPU再次切换,线程1也被挂起,轮到线程2进行if判断,还是可以进去

接下来,CPU切回线程0,线程0将票数-1,此时票数为0。再切回线程1,线程1继续-1,票数变成了-1最后切回线程2,再-1,变成了-2。这样就出现了线程安全问题。

对于线程安全问题,做法就是加锁,我这里没处理完,你就不要进来。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章