Java线程和他的六种状态

1 什么是线程

线程是操作系统中能过被运行调度得最小得单位,他被包含在进程之中,是进程得执行单位,一条线程指的是进程中一个单一顺序的控制流。一个进程可以并发多个线程,而多个线程可以并行执行不同得任务,总结就是:

  • 进程:是应用程序进入内存中,就是一个内存中运行的应用程序,比如QQ ,微信,网易云音乐
  • 线程:线程属于进程,是进程的一个执行单元,负责程序的执行。(应用程序通向CPU的执行路径,CPU可以通过这个路径执行功能,这个功能又叫线程。)
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位
  • 一个程序至少有一个进程,一个进程又至少有一个线程
  • 线程的划分程度小于进程,使得多线程程序的并发性更高
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

分时调度:

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度:

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

创建线程

创建线程有四种方法,继承Thread类,实现Runnable接口,这两种方式都是没有返回值的,而实现Callable接口是具有返回值的,并且可以抛出异常,第四种就是使用线程池来创建了,需要的时候直接用就可以了,减少资源的消耗,这篇博客有写的哦 ==> Java创建线程的四种方式。

2 并发和并行

  • 并发:两个事件或多个事件交替执行,在同一个时间段内发生。 重点在于他们是交替发生的,轮流交替,比如你一边喝水一遍吃零食,宏观上看你是同时在吃东西和喝水,但是实际上你是交替进行的,同一个时刻是做一件事情。
  • 并行:两个事件或多个事件在同一时刻发生 (同时发生), 重点是他们是同一个时刻发生的,比如你边写字边唱歌,这两件事情是在同一时刻发生的,这个就是并行,同时进行。

3 线程的状态

在Java中,线程被分成了六个状态,而在操作系统中,线程是被分成五个状态的,我们可以从java.lang.Thread.State类中可以看出:

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         * 线程的新生状态
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         * 线程运行中/等待
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         * 线程的阻塞状态
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         * 线程的等待态,死等,等,就应等
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         * 线程超时等待状态,超过一定的时间,就不在等待了
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         * 线程中止状态,代表线程执行完毕
         */
        TERMINATED;
    }

也就是说Java线程是由六个状态的:

  • NEW:新建状态
  • RUNNABLE:就绪/运行
  • BOLCKED:阻塞状态
  • WAITING:等待态
  • TIMED_WAITING:超时等待
  • TERMINATED:终止态

而操作系统的线程是只有五个状态的:

  • NEW:初始状态,对应Java中的NEW状态
  • READY:可运行状态,对应 Java中的 RUNNBALE状态
  • RUNNING:运行状态,对应 Java中的RUNNBALE 状态
  • WAITING:等待状态,该状态在 Java中被划分为了 BLOCKEDWAITINGTIMED_WAITING三种状态
  • TERMINATED:终止状态
名称解释
NEW当一个线程被new出来的时候,还没有执行start方法,就会处于该状态
RUNNABLEJava将线程中的就绪态(READY)和运行态(RUNNING)统一为这一种状态了,就是一个线程调用start方法后,要么是等待调度,要么是正在运行,一个线程被启动后,就会处于就绪态(READY),等待CPU的调度,只有CPU给他分配了时间片,他才会处于运行态(RUNNING)。
BOLCKED通常和锁有关系,表示线程正在尝试获取被上锁的资源,比如进入synchronized修饰的方法和代码块
WAITING当该线程执行wait方法或者join方法时,不带参数就表示永久睡眠,只能被其他线程唤醒,被唤醒后,看CPU是否空闲从而进入不同的状态
TIMED_WAITING调用wait方法或者是sleep方法,这个是携带参数的,睡眠指定的时间,在这里我们要注意的是,sleep方法是不会释放掉锁的,而wait方法会释放自己所占的锁资源的,所以说调用sleep方法后,时间到了,线程自动唤醒,就会处于RUNNABLE状态的花,否则,就要根据CPU是否空闲来决定处于哪一个状态了。
TERMINATED线程run方法完成了,或者说main方法结束了,我们就认为他是终止了,要注意的是,也许这个线程对象还存在,但是他不在是一个可以单独执行的线程了,线程一旦终止了,就不可以复生,就不可以在执行start方法啦, 当然,一个线程任何时候只能调用一次start方法,再一次调用的话,就会抛出IllegalThreadStateException异常。

4 Synchronized 和 lock锁

synchronized 锁是什么

synchronized是Java中的一个关键字,是一个同步锁,我们为什么要使用他呢,在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即方法或者代码块在运行的时候只有一个能够进入临界区,他有三种应用方式:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

同步代码块中锁对象

synchronized 代码块的使用方法如下:

synchronized(obj){  //obj是我们想要锁住的对象
	// 中间写执行的代码
}

这个锁住的对象是会有三种情况的:

  • this,也就是当前调用这个方法的对象
  • 类的class模板,也就是我们实例化对象的那个类
  • 我们自己定义的一个对象,可以是一个Sting,也可以是一个Object类型的。

synchronized同步代码块的锁的粒度会更加的小,可以锁住我们想要的代码,对于这一部分进行同步进行的要求,那来讲一下三个不同锁对象的区别:

  • 自己定义的对象的,就是我们可以划分出很多个代码块,分别加上不同的锁,这个时候只有锁对象是同一个对象的时候才会进行锁资源的抢夺,锁不同的话,是不会进行资源的抢夺的,或者说,一个对象,调用两个不同的方法,他们锁对象都是方法里面创建的名称为lock的对象,这个时候,调用这两个方法也是会异步执行的,两个方法是没有影响的,因为他们是两个锁对象。
  • this,是方法的调用者,也就是说,一个对象,调用了两个不同的方法,但是他们的锁对象是this的话,他们也是会发生阻塞的,进行锁资源的抢夺,因为他们锁的对象是方法的调用者。
  • class模板,class模板是一个类只有一份的,也就是说,如果两个对象调用不同的方法,但是他们的代码块的锁对象是class模板,那么他们也是会阻塞的,因为这个锁只有一份,是类的。

8锁问题

八锁问题就是synchronized锁的八种情况,对于多个线程执行不同的同步方法,静态方法,多个对象,然后线程是如何进行方法的调用的,锁的对象都是什么,锁是什么,都有什么区别,这一篇博客都有讲的哦,可以去看一哈子 ===> 八锁现象的简单介绍

synchronized锁的缺陷

  • synchronized锁修饰的代码块或者方法,一旦拿到了对应的锁,那么释放锁资源的方式只有执行完了代码块释放,或者出现异常,jvm让线程自动释放锁,但是这个时候如果这个获取锁的线程被阻塞了,没有及时释放锁,那么其他需要这个锁的线程只可能干巴巴的等着这个锁,那么这个操作是十分消耗系统资源的,而lock可以自主释放锁,不让线程无休止的等下去。
  • 多个线程读写一个文件时,应该是读读操作是兼容的,读写和写写操作是不兼容的,但是使用synchronized的话,读读操作都是不被允许的。

lock 锁是什么

lock锁,他是在jdk1.5之后,java.util.concurrent.locks包下的另外一种实现同步访问的技术,这个时候就会问了,有了synchronized锁了,为什么还有一个lock锁呢,因为lock锁可以做到synchronized锁做不到的东西,它可以很好的解决掉上面 synchronized 锁的缺陷。
通过查看他的源码可以知道,lock是一个接口:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

那现在我们来对这六个方法来做一个解释

方法名解释
lock()lock() 是最基础的获取锁的方法。 在线程获取锁时如果锁已被其他线程获取,则进行等待,是最初级的获取锁的方法,对于lock接口而言,获取锁和释放锁都是显示的,所以我们必须手动的去获取锁,通常获取操作我们在try{}代码块中进行
unlock()unlock()是我们手动释放锁的方式,我们释放锁操作是在finally中进行的,所以说正常的使用流程就是,try{}中获取锁,并且执行操作。cath()中检查异常抛出,finally{}中释放锁资源,尤其要注意的一点是,我们是必须要释放锁资源的,否则就会容易产生死锁。
tryLock()tryLock() 用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,返回 true,否则返回 false,代表获取锁失败。相比于 lock(),这样的方法显然功能更强大,可以根据是否能获取到锁来决定后续程序的行为 ,该方法的返回结果是会立即返回的,所以我们通常和if()语句一起使用
tryLock(long time, TimeUnit unit)tryLock() 的重载方法是 tryLock(long time, TimeUnit unit),这个方法和 tryLock() 很类似,区别在于 tryLock(long time, TimeUnit unit) 方法会有一个超时时间,在拿不到锁时会等待一定的时间,如果在时间期限结束后,还获取不到锁,就会返回 false;如果一开始就获取锁或者等待期间内获取到锁,则返回 true,这个方法就可以避免死锁的问题,因为我获取不到锁并且我的等待时间到了,那么我就会自己主动的释放锁,我不要了,诶,就是玩。
lockInterruptibly()这个方法的作用就是去获取锁,如果这个锁当前是可以获得的,那么这个方法会立刻返回,但是如果这个锁当前是不能获得的(被其他线程持有),那么当前线程便会开始等待,除非它等到了这把锁或者是在等待的过程中被中断了,否则这个线程便会一直在这里执行这行代码。一句话总结就是,除非当前线程在获取锁期间被中断,否则便会一直尝试获取直到获取到为止 ,它就相当于是一个无限等待的trylock()方法
newCondition()获取condition对象,可以执行对应的await()方法和signal()方法来进行线程间的通信

这里我们用一段代码来看看这些方法是如何使用的:

public class MyLock {

    private Lock lock = new ReentrantLock(); //定义一个全局的锁
    public static void main(String[] args) {
        final MyLock myLock = new MyLock(); //定义一个类调用方法
        new Thread(()->myLock.test(),"A").start();
        new Thread(()->myLock.test(),"B").start();
    }

    public void test(){
        //lock.lockInterruptibly();
        //可以在代码一开始就使用这个,直到获取到锁了,才执行,否则一直等待
        while (true){
            if(lock.tryLock()){  //尝试获取锁,得到啦
                System.out.println(Thread.currentThread().getName()+" ==> 获取到了锁");
                try {
                    //因为trylock尝试获取锁,返回true时就已经得到锁了,所以这里不需要在上锁,但是一般都是在try里面加锁
                    //lock.lock();
                    System.out.println(Thread.currentThread().getName()+"==>  我给自己加上lock锁");
                    Thread.sleep(10);
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+"==>  释放了lock锁");
                    lock.unlock();  //finally 里释放锁
                    break;
                }
            }else{
                System.out.println(Thread.currentThread().getName()+" ==> 还在苦苦等待");
                try {
                    TimeUnit.MICROSECONDS.sleep(1000); //让程序睡眠1ms,便于观察结构
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

代码的运行结果
从上图我们可以看出,我们开启了两个线程,线程A首先获取锁,然后这个时候休眠,那么线程B就会被调用,但是他一直获取不到锁,所以一直在苦苦等待,直到A释放了锁,这个时候线程B才可以拿到锁,释放。

lock 和 synchronized 的区别

  • lock不是java语言内置的,synchronized是java语言的关键字,因此是内置特性。lock是一个类,通过这个类可以实现同步访问。
  • ck和synchronized有一点非常大的不同,采用synchronized不需要用户手动的去释放锁,当synchronized方法或者代码块执行完毕之后,系统会自动的让线程释放对锁的占有,而lock则必须要用户去手动释放锁,如果没有主动的释放锁,就会可能导致出现死锁的现象。

5 线程通信

多个线程并发执行的时候,在CPU中是随机切换的,这个时候我们想多个线程一起来完成一件任务,这个时候我们就需要线程之间的通信了,多个线程一起来完成一个任务,线程通信一般有种方式。

  • 利用 volatile 关键字
  • 利用 Object类的 wait/notify 方法
  • 通过 condition 的 await/signal 方法
  • 通过 join 的方式

这个我两天内会写一篇比较详细的博客的,啦啦啦啦,现在晚上,休息,先把这一篇发出来。

6 线程中的常用方法

方法名解释
start()线程启动
sleep(long millis)线程睡眠指定时间
getName()()返回线程的名字
wait()线程等待,直到其他线程调用notify()方法唤醒,他们的锁必须是同一个
notify()随机唤醒一个线程,他们的锁必须是同一个
notifyAll()会唤醒所有线程,他们的锁必须是同一个
join()插队,例如A线程调用了B线程的join方法,那么只有B线程结束了,A线程才可以继续运行
yield()就是当前线程会让出CPU,然后开始再一次抢夺,可能他又一次抢到了CPU,反正就是我让了,你们抢不到,怪我咯
setPriority()设置线程的优先级,数字越大,优先级越高,优先级是1-10,要注意的是并不是线程优先级越高,就一定会先执行,只是概率会很大

小结

线程的内容我现在写的就是这些了,为什么写了,最主要的还是因为我自己也不是特别了解了,在熟悉一下,加深印象,当然,写的不好,希望可以指出来哦,后面会逐渐完善,加新的内容滴。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值