多线程初阶篇

一、多线程的创建方式?

1、通过继承Thread类创建线程类
①定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
②创建Thread子类的实例,也就是创建了线程对象
③启动线程,即调用线程的start()方法

class MyThread extends Thread {
     @Override 
     public void run() { 
         System.out.println("这里是线程运行的代码"); 
     } 
 }
 MyThread t = new MyThread();
  t.start(); // 线程开始运行

2、通过实现Runnable接口
1、定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3、第三部依然是通过调用线程对象的start()方法来启动线程

class MyRunnable implements Runnable { 
    @Override 
    public void run() {
         System.out.println(Thread.currentThread().getName() + "这里是线程运行的代码"); 
     } 
 }
 Thread t = new Thread(new MyRunnable()); 
 t.start(); // 线程开始运行

3、实现Callable接口和Future创建线程

public class MyCallable implements Callable<Integer>{
    private int number;
    public MyCallable(int number){
        this.number = number;
    }
    public Integer call() throws Exception{
        int sum = 0;
        for(int i=1;i<number;i++){
            sum += i;
        }
        return sum;
    }
}

三种方式的区别和联系
1、定义Runnable接口的实现类,一样要重写run()方法,run()中都是线程执行的实体。
2、继承Thread有个弊端, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
3、Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
4、Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值

二、多线程的几个常见属性

在这里插入图片描述
①ID 是线程的唯一标识,像人的身份证号一样,不同线程不会重复
②名称是各种调试工具用到
③状态表示线程当前所处的一个情况,下面会解释
④优先级高的线程理论上来说更容易被调度到,注意是理论上,不是一定
⑤关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。这个选择题经常遇到
⑥是否存活,即简单的理解,为 run 方法是否运行结束了

三、多线程的常用方法

1、start();调用这个方法之后,多线程才真正开始工作,调用了它的run()方法
2、interrupt():中断线程的工作
3、join():等待一个线程
4、currentThread():获取当前线程的引用,返回执行当前代码的线程
5、sleep():休眠当前线程,让线程先睡一会
6、run():里面是代码实现部分,也就是你想让这个线程去帮你做什么事
7、yield():主动释放当前线程的执行权,像老好人一样给别人说你先来
这里有相应的代码示例,所以上面没有详细介绍

wait和sleep的区别

  1. wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对像上的 monitor
    lock
  2. sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求。
  3. wait 是 Object 的方法
  4. sleep 是 Thread 的静态方法

四、多线程的状态

在这里插入图片描述
初始(new)
新创建好一个线程对象,但还没有调用start()方法时就处于初始状态

运行(RUNNABLE)(就绪(ready)和运行中(running))
哪些情况下会进入就绪?
1、调用线程的start()方法,此线程进入就绪状态。
2、当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
3、当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
4、锁池里的线程拿到对象锁后,进入就绪状态。
5、线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

阻塞(BLOCKED)
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

等待(WAITING)
进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

扫描二维码关注公众号,回复: 11520863 查看本文章

超时等待(TIMED_WAITING)
该状态不同于WAITING,它可以在指定的时间后自行返回。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

终止(TERMINATED)
表示该线程已经执行完毕。
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

五、线程不安全

原因:多个线程对同一个共享数据进行操作时,线程没来得及及时更新共享数据,导致别的线程拿到的不是最新数据,所以就出现了线程不安全

原子性:它的作用效果不能被中间断开
什么是原子性:我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
一条 java 语句不一定是原子的,也不一定只是一条指令

不保证原子性会给多线程带来什么问题
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
可见性:高速缓存也带来一个问题,你执行的n++的中间值,全部只保留在高速缓存中,内存中是没有及时变化的,这会导致一个问题,如果有多个cpu,(每个CPU都会有自己的高速缓存,而cpu之间共性数据是用内存的,当前计算的结果,其他cpu上看不到)
代码顺序性:指令的最终执行顺序,不是你代码的顺序。目的是提高执行效率
谁会对代码进行重排序?
1.编译器就有可能进行重排序(根据它自己知道的信息,重排序代码)
2.运行阶段,JYM也有可能进行重排序(运行起来之后,JVM知道的信息要比编译器多) - - JIT (Just In Time)
3.运行阶段,CPU指令上也会进行- -定的重排序。

六、怎样解决线程不安全?

(接下来博客详细更新)
1、synchronized锁(偏向锁,轻量级锁,重量级锁)
2、volatile锁,只能保证线程之间的可见性,但不能保证数据的原子性
3、jdk1.5并发包中提供的Atomic原子类
4、Lock锁

猜你喜欢

转载自blog.csdn.net/chris__x/article/details/107188890