欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程基础知识回顾

程序员文章站 2022-05-05 23:13:08
...
多线程的基础知识复习

1)进程和线程的区别

进程:
    程序运行后,一个QQ,微信等就是一个进程。
线程:
    线程是进程中的最小单元。说简单的话说,线程就是程序中不同的执行路径。
程序:
    QQ是一个程序,是一个硬盘上的程序,


2)线程run方法和start方法的区别

public class T01_WhatIsThread {
    //新建了静态内部类继承Thrread,线程修改1秒,输入T1
    private static class T1 extends Thread {
        @Override
        public void run() {
           for(int i=0; i<10; i  ) {
               try {
                   TimeUnit.MICROSECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("T1");
           }
        }
    }
    //main方法
    public static void main(String[] args) {
        new T1().run();
        //new T1().start();
        for(int i=0; i<10; i  ) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main");
        }

    }
}
输出
T1
T1
main
main
//这个时候注释18run方法 开始start方法 执行结果大不一样
T1
main
T1
main

#结论 therad的start方法 执行路径是分支的形式,而run方法是重上到下依次执行。

3)多线程的常用实现方法

//线程主要实现的方法有3种
public class T02_HowToCreateThread {
    //集成Thread的方法
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }
    //实现Runnable接口
    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }
    //三种线程不同的运行方式
    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        //lamda表达式来执行一个线程
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
    }
 ![https://img-blog.csdnimg.cn/20191030175728242.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3c2ODMzNTM5Nw==,size_16,color_FFFFFF,t_70](https://img-blog.csdnimg.cn/20191030175728242.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3c2ODMzNTM5Nw==,size_16,color_FFFFFF,t_70)
}
// lamda表达式也是一种方式,线程池也是一种方式
//Executors.newCachedThreadPool();
//通过线程池去拿到一个线程,而这个线程还是要执行runable或者start的方法。

4)线程最基本的方法

  • 4.1 sleep
    • 当前线程睡眠多少时间,由其他线程来执行
  • 4.2 join
    • 常用于等待另外一个线程结束,也就是保证线程有序执行。
      多线程基础知识回顾
     //测试join线程
    static void testJoin() {
        Thread t1 = new Thread(()->{
            //线程1创建了10个线程
            for(int i=0; i<10; i  ) {
                System.out.println("A"   i);
                try {
                    //每个线程休眠500毫秒
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //线程2
        Thread t2 = new Thread(()->{
            try {
                //线程1的线程加入。等待线程1结束
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程1结束后,才开始执行此代码
            for(int i=0; i<10; i  ) {
                System.out.println("B"   i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //分别启动
        t1.start();
        t2.start();
    }
    

#执行结果
A0
A1
A2
A3
A4
A5
A6
A7
A8
A9
B0
B1
B2
B3
B4
B5
B6
B7
B8
B9
```

  • 4.3 yield
    • 当前线程先离开,返回到就绪的状态,至于这个线程是否会被CPU马上执行,还是先执行等待队列中已经等待的线程,这个不一定。看竞争
线程的状态图

多线程基础知识回顾

  • Ready(就绪) Running(正在运行) 都属于Runnable状态。
  • TimedWaiting 的相关方法是指时间一到,就会自动恢复runnable状态
  • Waiting 状态是指必须被唤醒才能进入Runnabel状态
  • synchronized 得到同步代码块的锁 之前会进入阻塞状态,得到锁之后,线程运行
  • Terminated 线程停止
  • 不建议用Thread的stop方法来强行停止线程,有安全问题。
  • 对于interrupt的一些说明:
#线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;
#调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
#我们会catch这个异常,再根据业务逻辑去处理线程的后续行为。
#代码示例
@Override
public void run() {
    try {
        // 1. isInterrupted()保证,只要中断标记为true就终止线程。
        while (!isInterrupted()) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
    }
}
//interrupt 用于控制业务场景的用法极少,正常用法一般是某一个线程阻塞时间很长很长,通过interrupt来打断线程。
  • 线程状态的小例子
static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(this.getState());

            for(int i=0; i<4; i  ) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        Thread t = new MyThread();

        System.out.println(t.getState());

        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(t.getState());

    }
    #输出结果
    NEW
    RUNNABLE
    0
    1
    2
    3
    TERMINATED
synchronize 锁
  • 需要注意的是 锁,所的是对象,而不是代码

    • 比如:

    public class T {
    private int count = 10;
    private Object o = new Object();
    public void m() {
    synchronized(o) { // 任何线程要执行下面的代码,必须先拿到o的锁
    count–;
    System.out.println(Thread.currentThread().getName() " count = " count);
    }
    }
    }
    #问题
    上述代码如果new了2个不同的object,o和o1,那synchronize(o) 是锁誰呢,还是锁o。

      ```
    
  • synchronize 的几种锁的形式

    • 锁this
    private int count = 10;
    public void m() {
        synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
            count--;
            System.out.println(Thread.currentThread().getName()   " count = "   count);
        }
    }
    
    • 锁方法
    private int count = 10;
    public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
        count--;
        System.out.println(Thread.currentThread().getName()   " count = "   count);
    }
    
    • 锁静态方法
    private static int count = 10;
    public synchronized static void m() { //这里等同于synchronized(T.class)
        count--;
        System.out.println(Thread.currentThread().getName()   " count = "   count);
    }
    
    public static void mm() {
        synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?(不可,因为没有new,)
            count --;
        }
    }
    
    • 小思考
      1. 线面的线程如何输出?
        2)加上synchronize后又有什么区别
        3)加上volatile后又什么区别
    private /*volatile*/ int count = 100;
    
    public /*synchronized*/ void run() { 
        count--;
        System.out.println(Thread.currentThread().getName()   " count = "   count);
    }
    
    public static void main(String[] args) {
        T t = new T();
        for(int i=0; i<100; i  ) {
            new Thread(t, "THREAD"   i).start();
        }
    }
    #1 打印列会出现重复或者实际减的数和打印的数不一致。
    //打印结果抽取异常部分
    THREAD81 count = 37
    THREAD70 count = 37
    #2 synchronize 既保证可见又保证一致性
    #3 volatile 保证可见性,这个变量改后立马被线程发现。
    #4 加了synchronize就不必加volatile。
    
    • 同一个线程可以同时掉加锁和不加锁的方法,并不会导致一个方法有锁,而导致不加锁的方法无法执行。

    • 读方法和写方法是否都需要上锁来保持一致,看具体的业务逻辑。如果写上锁,读不上锁,有可能脏读数据,这个根据业务来定。读上锁会让读取的效率大大降。

    • synchronize 是可重入锁,如果上锁的方法1,调用上锁的方法2,如果不可以重入,会产生死锁。

    • \color{red}{程序如果出现异常,默认情况下锁会被释放}

    public class T {
    int count = 0;
    synchronized void m() {
    	System.out.println(Thread.currentThread().getName()   " start");
    	while(true) {
    		count   ;
    		System.out.println(Thread.currentThread().getName()   " count = "   count);
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		if(count == 5) {
    			int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
    			System.out.println(i);
    		}
    	}
    }
     }}
     public static void main(String[] args) {
    			T t = new T();
    			Runnable r = new Runnable() {
    
    			@Override
    			public void run() {
    				t.m();
    			}
    			
    		};
    		new Thread(r, "t1").start();
    		
    		try {
    			TimeUnit.SECONDS.sleep(3);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		new Thread(r, "t2").start();
    	}
     }
     #//打印结果 人为异常不补货,t2得到锁。继续执行
    t1 count = 2
    t1 count = 3
    t1 count = 4
    t1 count = 5
    t2 start
    Exception in thread "t1" t2 count = 6
    java.lang.ArithmeticException: / by zero
    	at com.mashibing.juc.c_011.T.m(T.java:27)
    	at com.mashibing.juc.c_011.T$1.run(T.java:39)
    	at java.base/java.lang.Thread.run(Thread.java:844)
    t2 count = 7
    
  • synchronize\color{red}{synchronize的底层升级概念}
    推荐文章 锁升级的小段子
    偏向锁(谁来谁第一,谁偏向)----》
    竞争 自旋锁,(自旋10次或者 JDK目前规定为自旋线程超过CPU内核数的一半)
    如果超过了自旋的上限,就升级重量级锁,重锁是OS(操作系统)级别的锁,并进入等待队列。

    • 锁使用的场景:
      线,\color{red}{线程少,上锁代码执行快,用自旋锁}
      线,OS\color{red}{线程多,上锁代码执行长,用OS重量锁}
  • 总结
    线程的概念、线程的启用方式,常用的方法介绍。
    线程的状态机、
    synchronize 锁的是对象,不是代码。
    synchronize 锁的集中方式
    synchronize 的锁的升级。
    异常锁,默认放弃锁。除非catch跳过循环。
    偏向锁、自旋锁 (公平、非公平)重量级锁。

  • 作者QQ 68335397 有问题指正或者讨论学习,可以撩,博客会定期回复。感谢大家。