【java复习】线程深入理解

线程对象只能启动一个对象

示例代码

/*
 * 演示 使用线程的注意事项
 * 
 * */
package com.fox.test1;

public class Demo11_1 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Cat cat1 = new Cat();
        //Dog dog1 = new Dog();
        cat1.start();
        cat1.start();

    }

}


class Cat extends Thread{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("11");
    }
}


class Dog implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("22");
    }
}

程序运行时报错如下:

程序运行时报错如下:
11
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at com.fox.test1.Demo11_1.main(Demo11_1.java:14)

先打出了11,再次启动线程时报错了.

/*
 * 演示 使用线程的注意事项
 * 
 * */
package com.fox.test1;

public class Demo11_1 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Cat cat1 = new Cat();
        Dog dog1 = new Dog();
        //cat1.start();
        //cat1.start();
        Thread t = new Thread(dog1);
        t.start();
        t.start();

    }

}


class Cat extends Thread{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("11");
    }
}


class Dog implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("22");
    }
}

上面的代码运行后报错:

22
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at com.fox.test1.De

正确的启动代码如下:

/*
 * 演示 使用线程的注意事项
 * 
 * */
package com.fox.test1;

public class Demo11_1 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Cat cat1 = new Cat();
        Dog dog1 = new Dog();
        //cat1.start();
        //cat1.start();
        Thread t1 = new Thread(dog1);
        Thread t2 = new Thread(dog1);
        t1.start();
        t2.start();

    }

}


class Cat extends Thread{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("11");
    }
}


class Dog implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("22");
    }
}

结论:不管是继承Thread还是实现Runnable接口创建线程,它们的对象只能启动一次,否则有异常抛出。

线程安全问题

模拟机票售票系统,有三个售票点,在一天卖出2000张票,注意不是每个售票点都卖2000张票,而是三个售票点一共卖出2000张票。

/*
 * 演示模拟售票系统
 * 
 * */

package com.fox.test2;

public class Demo11_2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TickWindow tw1 =new TickWindow();
        TickWindow tw2 =new TickWindow();
        TickWindow tw3 =new TickWindow();
        Thread t1 = new Thread(tw1);
        Thread t2 = new Thread(tw1);
        Thread t3 = new Thread(tw1);
        t1.start();
        t2.start();
        t3.start(); 
    }
}


//定义售票窗口类
class TickWindow implements Runnable
{
    private  int sums = 2000;

    @Override
    public void run() {
        while(true)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if(sums>0)
            {
                System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
                sums--;
            }else {
                //售票结束
                break;
            }   
        }   
    }
}

运行结果:

Thread-1正在卖出第2000张票
Thread-2正在卖出第2000张票
Thread-0正在卖出第2000张票
Thread-2正在卖出第1997张票
Thread-0正在卖出第1997张票
Thread-1正在卖出第1997张票
Thread-1正在卖出第1994张票
Thread-2正在卖出第1994张票
Thread-0正在卖出第1994张票
Thread-2正在卖出第1991张票

问题:三个窗口卖出都是同一张票,如何解决线程安全问题,如何解决线程并发问题?
在上面的模拟机票售票系统中,极有可能出现同一张票被多个窗口售出的情况,出问题的代码就是:
if(sums>0){
System.out.println(Thread.currentThread().getName()+”正在卖出第”+sums+”张票”);(1)
sums–;(2)
}

假如现在num=1,a线程刚刚执行完语句(1),正要执行语句(2)时,b线程执行if(sums>0)因为这时sums还是1,所以b线程也会执行语句(1),这样就相当于一张票卖了两次,这样的多并发就给我们带来了麻烦。

解决问题的关键在于,保证容易出问题的代码的原子性,所谓的原子性就是指,当a线程在执行某段代码时别的线程必须等待,等a线程执行完问题代码后b线程才能执行这段代码。

Java代码中处理线程同步的方法非常简单,只需要在需要代码同步的代码段中,用synchronized(object){ 需要同步的代码段 }即可。

修改代码:

/*
 * 演示模拟售票系统
 * 解决线程同步的安全问题
 * */

package com.fox.test2;

public class Demo11_2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TickWindow tw1 =new TickWindow();
        TickWindow tw2 =new TickWindow();
        TickWindow tw3 =new TickWindow();
        Thread t1 = new Thread(tw1);
        Thread t2 = new Thread(tw1);
        Thread t3 = new Thread(tw1);
        t1.start();
        t2.start();
        t3.start(); 
    }
}


//定义售票窗口类
class TickWindow implements Runnable
{
    private  int sums = 2000;

    @Override
    public void run() {
        while(true)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            synchronized(this){
                if(sums>0)
                {
                    System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
                    sums--;
                }else {
                    //售票结束
                    break;
                }
            }
        }   
    }
}




运行结果:
Thread-0正在卖出第2000张票
Thread-1正在卖出第1999张票
Thread-2正在卖出第1998张票
Thread-0正在卖出第1997张票
Thread-1正在卖出第1996张票
Thread-2正在卖出第1995张票
Thread-0正在卖出第1994张票
Thread-1正在卖出第1993张票
Thread-2正在卖出第1992张票
Thread-0正在卖出第1991张票
Thread-1正在卖出第1990张票
Thread-2正在卖出第1989张票

上面的synchronized传入的对象是this,这里的对象可以是任意的对象。

/*
 * 演示模拟售票系统
 * 解决线程同步的安全问题
 * */

package com.fox.test2;

public class Demo11_2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TickWindow tw1 =new TickWindow();
        TickWindow tw2 =new TickWindow();
        TickWindow tw3 =new TickWindow();
        Thread t1 = new Thread(tw1);
        Thread t2 = new Thread(tw1);
        Thread t3 = new Thread(tw1);


        t1.start();
        t2.start();
        t3.start(); 
    }
}


//定义售票窗口类
class TickWindow implements Runnable
{
    private  int sums = 2000;
    Dog dog1 = new Dog();
    @Override
    public void run() {
        while(true)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            synchronized(dog1){
                if(sums>0)
                {
                    System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
                    sums--;
                }else {
                    //售票结束
                    break;
                }
            }
        }   
    }
}

class Dog{

}

上面的代码中synchronized中的对象用的是一个普通的dog对象。也可以解决线程安全问题。

线程同步的深入理解

Java任意类型的对象都有一个标志位,该标志位有0,1两种状态。其起始状态为1,当某个线程执行了synchronized(object)语句后,object对象的标志位变为0状态,直到执行完整个synchronized语句内的代码块后,object对象的标志位恢复为1状态。
当另一个线程执行到同样的synchronized(object)语句时,先检查object对象的标志位是否为1,如果是0状态,就表明有其他的线程在执行synchronized(object)语句内的代码块,那么该线程就会被阻塞让出CPU资源,直到另外的线程执行完相同的同步代码块后将状态恢复为1,等待线程的阻塞状态取消,线程继续运行,同时该线程又会将object对象的标志位设置为0防止其他线程再次进入相同的同步代码块中。
对同步机制的解释:如果有多个线程因等待同一个对象锁而处于阻塞状态时,当该对象的标志位恢复到1状态时,只会有一个线程能够进入同步代码的执行,其他的线程仍然处于阻塞状态.
object可以是任意类型的对象。
对象的标志位用术语将就是对象锁。

猜你喜欢

转载自blog.csdn.net/yuewen2008/article/details/81778822
今日推荐