1.线程和进程的关系

  • 概念:
    线程,程序执行流的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。
    简单来说:一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
  • 例子:
    音乐播放器在电脑中运行,他可以就看成一个进程,而线程就可以看作音乐播放器中的负责各个模块的执行者例如:负责图片的显示,音乐的播放,歌词的显示等都可以看作是一个线程。一个程序中必然有一个进程为主进程,一个进程必然有一个线程

2.线程的创建方式

⚪继承Thread类创建线程

public class MyThread extends Thread{  //继承Thread类
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

⚪实现Runnable接口创建线程

public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者  new Thread(new MyThread2()).start();
  }
}

⚪使用Callable和Future创建线程
从Java 5开始,Java提供了Callable接口,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

  • 1.call()方法可以有返回值;
    2.call()方法可以声明抛出异常。
  • 总结:
    1.继承Thread类,实现Runnable接口两者本质上区别不大,因为java只能单继承的原因导致不灵活,所以平常使用Runnable
    2.实现Runnable接口和实现Callable接口的方式多个线程可以共享一个target对象,比较适用多个相同线程处理同一份资源的情况。
    3.Thread和Runnable使用到了代理模式,Thread代理了Runnable接口,并在Thread又拓展了许多控制线程的方法。

3.线程的生命周期

java 进程 线程 标识 java进程和线程的关系_java

  • 生命周期(五种基本状态)
    新建状态(New):当线程对象创建后,即进入新建状态,如:Thread t = new MyThread();
    就绪状态(Runnable):当调用线程对象的start()方法时,线程即进入就绪状态。处于就绪状态的线程只是说明此线程已经做好准备,随时等待CPU调度执行,并不是说执行了start()方法就立即执行。
    运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
    阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
    死亡状态: 线程执行完毕或者是异常退出,该线程结束生命周期。

详细介绍:

  • 1. 初始状态
    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
  • 2.就绪状态
    就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
    调用线程的start()方法,此线程进入就绪状态。
    当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    锁池里的线程拿到对象锁后,进入就绪状态。
  • 3. 运行状态
    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
  • 4. 阻塞状态
    阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
    阻塞状态分类:
    等待阻塞: 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    同步阻塞: 线程在获取synchronized同步锁失败(因为锁被其它线程占用),它会进入到同步阻塞状态;
    其他阻塞: 通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪
  • 5. 等待
    处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
  • 6. 超时等待
    处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
  • 7. 终止状态
    当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
    在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

4.线程状态控制

  • ⚪Thread-sleep():
    1.sleep()会让当前的线程暂停一段时间,并进入阻塞状态,调用sleep并不会释放锁。
    2.sleep()是静态方法,故不要用线程实例对象调用它,它睡眠的始终是当前正在运行的3.线程,而不是调用它的线程对象。
    3.线程实际休眠时间会大于设定的时间。
    4.sleep()方法声明抛出InterruptedException,所以调用sleep()需要捕获异常。
  • ⚪Thread-yield():
    调用yield()方法之后,从运行状态转换到就绪状态,CPU从就绪状态队列中只会选择与该线程优先级相同或者是优先级更高的线程去执行。-------yield()方法不需要抛出异常。但是礼让不一定成功,还有可能又是他
  • ⚪Thread-join():
    线程合并就是将几个并发线程合并为一个单一线程执行,应用场景就是当一个线程的执行必须是要等到其他线程执行完毕之后才能执行。

⚪wait()和notify :
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
⚪sleep()和yield()的区别:
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。 实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程;另外sleep 方法允许较低优先级的线程获得运行机会,

⚪wait和sleep区别:

  • 共同点:
    他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
    wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    注意点: InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
  • 不同点:
    Thread类的方法:sleep(),yield()等---------- Object的方法:wait()和notify()等
    每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用;所以sleep()和wait()方法的最大区别是:sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

5.多线程的概念

进程可以简单的理解为一个可以独立运行的程序单位。它是线程的集合,进程就是有一个或多个线程构成的,每一个线程都是进程中的一条执行路径。
那么多线程就很容易理解:
1.多线程就是指一个进程中同时有多个执行路径(线程)正在执行。
2.多线程指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务

  • 例子: 首先进程是可以看作实际运行的一个应用程序,而线程是进程中的实际运行单位,
    例如音乐播放器就是一个进程,功能中的音乐播放,歌词展示等这都是被看作是一个线程,而后进程中有多个不同的线程在执行不同的任务,那这就是多线程。

6.为什么要是用多线程?

  • 1.在一个程序中,如果使用单线程,那么程序就必须等待这些操作执行完成之后才能执行其他操作。例如播放器,那么久不能是实现一边播放歌曲一边展示歌词,使用多线程,可以在将耗时任务放在后台继续执行的同时,同时执行其他操作。
    2.可以提高程序的效率。
  • 1.使用太多线程是很耗系统资源,因为线程需要开辟内存更多线程需要更多内存。
    2.影响系统性能,因为操作系统需要在线程之间来回切换。
    3.需要考虑线程操作对程序的影响,如线程挂起,中止等操作对程序的影响。
    4.线程使用不当会发生很多问题。
  • 总结:
    多线程是异步的,但这不代表多线程真的是几个线程是在同时进行,实际上是系统不断地在各个线程之间来回的切换(因为系统切换的速度非常的快,所以给我们在同时运行的错觉)

7.守护线程和用户线程的区别

1.如果JVM中所有的线程都是守护线程,那么JVM就会退出,进而守护线程也会退出。
2.如果JVM中还存在用户线程,那么JVM就会一直存活,不会退出。

  • 由此可以得到:
    ●守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
    ●用户线程是独立存在的,不会因为其他用户线程退出而退出。
    ●只要任何非守护线程还在运行,守护线程就不会终止

8.多线程安全问题

  • *简单测试你的线程是否安全:
    如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
    如果不一致,那就可能是多线程的资源安全有问题,常见的情况如下:
  • *临界资源问题:
    多个线程同时访问相同的资源并进行读写操作
  • *解决思路:
    避免竞态条件,多线程同步,必须获得每一个线程对象的锁(lock)

为什么要线程同步—解决线程安全问题

  • 举例:
    因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。

多线程安全问题怎么解决-----线程同步

  • 1.同步方法:
    即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
  • 2.同步代码块:
    即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
    注: 同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
  • 3.使用特殊域变量(volatile)实现线程同步
  • 思路
    a.volatile关键字为域变量的访问提供了一种免锁机制
    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
  • 注意: 因为volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。 它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是最新的。这样就保证了同步。
  • 4.使用重入锁实现线程同步
  • 使用重入锁实现线程同步。使用类ReentrantLock类来定义锁,其中lock()方法为打开锁,unlock()方法为关闭锁类似于synchronized修饰方法有一样功能。使用锁时间需要注意应该及时放开锁,不然会进入死锁状态。一般是在finally中释放锁
  • 注意:
    如果synchronized关键字能满足用户的需求就用synchronized,因为它能简化代码 。如果需要更高级的功能就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
  • 5.使用ThreadLocal局部变量实现线程同步
  • 如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
  • 现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,只是名字相同而已。所以就会发生上面的效果。

ThreadLocal和synchronized区别

  • synchronized:
    原理:同步机制采用以“时间换空间”的方式,只提供了一份变量,让不同的线程排队访问;
    侧重点:多个线程之间访问资源的同步。
  • ThreadLocal:
    原理:ThreadLocal采用"以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰;
    侧重点:多线程中让每个线程之间的数据相互隔离。

ThreadLocal是什么?有哪些用途?


  • 总结:
    线程并发:在多线程并发的场景下
    传递数据:我们可以通过Thr eadLocal在同一线程,不同组件中传递公共变量
    线程隔离:每个线程的变量都是独立的,不会互相影响
  • 常用方法:
•  ThreadLocal():创建ThreadLocal对象
 public void set( T value):设置当前线程绑定的局部变量
 public T get():获取当前线程绑定的局部变量
 public void remove():移除当前线程绑定的局部变量