线程安全与线程状态

 1.线程状态

线程共有有五种状态:新建;就绪;阻塞;运行;死亡

新建状态:通过new Thread()方法创建的新线程状态

就绪状态:通过start()方法启动之后进入就绪状态,并不立即执行,等待CPU调度

阻塞状态:通过sleep()方法或者同步锁使线程自运行状态转换为阻塞状态

运行状态:线程获得CPU调度开始执行

死亡状态:线程执行完毕进入死亡状态

状态转换图为:

线程安全与线程状态

join()方法同样可以使线程从运行进入阻塞状态

public class Text {
    public static void main(String[] args) throws InterruptedException {
        RunnableDome runnableDome=new RunnableDome();
        //创建实现类的对象
        Thread thread=new Thread(runnableDome);
        thread.join();
        //将此线程加入当前线程,即main方法所执行的线程执行完毕之后,再执行thread线程
        //将实现类的对象作为参数传入Thread对象
        thread.start();
        
    }
}

sleep()方法代码实现为:

public class TicketThread extends Thread {
    static int number=10;
    static Object obj=new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                try {
                    Thread.sleep(1000);
//使程序进入休眠状态,sleep()方法参数为毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName()+"买到了票"+number);
                    number--;
                }
                else {
                    break;
                }
            }
        }

    }

 2.线程安全

2.1synchronized 关键字(隐式的同步)

引发线程安全的原因主要有两点,一是对共享资源的竟用,二是多条线程共同操作共享资源;synchronized的出现就是为了满足解决此类问题的需求,在java中,sycchronized可以实现在同一时刻,只有一个线程可以执行某个方法或代码块。在此线程进入共享资源后,自动加锁,其余线程在外等待,执行完毕后,自动解锁。这个过程是一个隐式的过程。synchronized的三中应用方式分别为:

 这里先给出买票的一个测试用例,三种应用方式的测试类相同代码实现为:
 


public class Text {
    public static void main(String[] args) {
        TicketThread ticketThread1= new TicketThread();
        ticketThread1.setName("窗口1");
        ticketThread1.start();
        TicketThread ticketThread2=new TicketThread();
        ticketThread2.setName("窗口2");
        ticketThread2.start();
        System.out.println(ticketThread1.getClass()==ticketThread2.getClass());

    }
}

synchronized修饰代码块

指定加锁对象,在线程获取该对象的锁之后,其余线程无法获取,注意此时这个对象应当是唯一的

代码实现:   

public class TicketThread extends Thread {
    static int number=10;
    static Object obj=new Object();
    @Override
    public void run() {
        while (true){
//修饰代码块,且传入的对象(obj)是唯一的
            synchronized (obj){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName()+"买到了票"+number);
                    number--;
                }
                else {
                    break;
                }
            }
}
}

synchronized修饰实例方法 

此时synchronized默认的锁标志即传入的对象为this,注意如果此时有多个线程同时访问时,是允许的,因为各自线程对象的锁时不同的,如若此时访问的是共享资源,那么线程安全则不能被保证

public class TicketThread extends Thread {
    static int number=10;
    static Object obj=new Object();
    @Override
    public void run() {
        ticket();
    }
    /*
            窗口1买到了票10
            窗口2买到了票10
            窗口1买到了票9
            窗口2买到了票8
            窗口1买到了票7
            窗口2买到了票6
            窗口1买到了票5
            窗口2买到了票4
            窗口1买到了票3
            窗口2买到了票2
            窗口1买到了票1
    上述结果的出现就是因为,synchronized修饰成员方法时,锁标志默认的对象是this,每一个线程进去都会加上自己的一把锁,
    这把锁对于其它线程是无效的,因此可能会出现第一个线程进去,还未做number--操作时时,第二个线程进去

     */

    public synchronized  void ticket() {
        while(true){
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"买到了票"+number);
                number--;
            }
            else break;
        }
    }
}

synchronized修饰静态方法 

注意此时与修饰成员方法不同的点是此时synchronized锁标志即传入的对象为:class对象,此时即使是多个线程访问这一共享资源,也是线程安全的;因为在同一个类中,多个线程的class对象是相同的,即所加的锁对所有线程都有效,System.out.println(ticketThread1.getClass()==ticketThread2.getClass());// 可用此语句判断两个线程对应的Class对象是否相同;

代码实现:

public class TicketThread extends Thread {
    static int number=10;
    static Object obj=new Object();
    @Override
    public void run() {
       
        ticket();
    }
    
   /*
    修饰静态方法时则不会出现此类情况,原因是修饰静态方法时,锁标志的对象默认为对应的Class对象,只要两个线程对象是
    同一个类的对象,则对应的Class对象相同,System.out.println(ticketThread1.getClass()==ticketThread2.getClass());
    可用此语句判断两个线程对应的Class对象是否相同
    则这把锁对两个线程都有作用,当一个线程进去时,锁关闭,另一个线程则在阻塞状态。
    */

    public synchronized  void ticket() {
        while(true){
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"买到了票"+number);
                number--;
            }
            else break;
        }
    }
}

2.2 Lock(显式的)

Lock主要是通过Lock接口的实现类ReentrantLock来实现线程安全;与synchronized关键字的不同是需要手动关闭与释放锁,加锁的方法为lock();释放的方法为unlock();注意在发生异常时,不会自动释放锁,因此一般来说,使用Lock必须在try{}catch{}块中进行,且释放锁必须放在finally块中进行,确保所以定被释放,防治死锁的出现

代码实现为:

import javax.swing.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentantLockDome extends Thread {
    static int number = 10;
    static Lock lock=new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁,使代码快进入锁定状态,其余的线程排队等候
                Thread.sleep(50);
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "买到了票" + number);
                    number--;
                }else {
                    Thread.currentThread().stop();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                lock.unlock();
            }
        }
    }

}

测试类为 :

import synchronzied.TicketThread;

public class Text {
    public static void main(String[] args) {
        ReentantLockDome r1=new ReentantLockDome();
        ReentantLockDome r2=new ReentantLockDome();
        r1.setName("窗口1");
        r2.setName("窗口2");
        r1.start();
        r2.start();
    }
}

上一篇:Java多线程环境下的同步机制Synchronized


下一篇:Codeforces Round #278 (Div. 1) Strip (线段树 二分 RMQ DP)