Java多线程详解

七、线程安全问题

八、线程安全问题的解决——Synchronized同步机制

九、线程安全的单例模式之懒汉式

十、死锁问题

十一、线程安全问题的解决——Lock锁

十二、Lock锁和Synchronized的比较

十三、wait()、notify()、notifyAll()

十四、sleep()和wait()的异同

十五、用实现Callable接口的方式创建多线程

十六、使用线程池创建多线程


一、程序、进程、线程

  1. **程序(program):**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

  2. **进程(process):**是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的生命周期。

  3. **线程(thread):**进程可进一步细化为线程,是一个程序内部的一条执行路径。一个进程可以包含多个线程。

二、并行和并发的概念

  1. **并行:**是指同一时刻多个任务同时在运行,是真意义上的同时运行。

  2. **并发:**是指多个任务交替使用CPU,从这个时间段上看似乎这些任务在同时运行,但实际上在某一时刻只有一个任务在运行。

三、用继承Thread类的方式创建多线程

步骤:

  1. 定义一个子类继承Thread类。

  2. 子类中重写run()方法。

  3. 在主线程中new一个子类对象。

  4. 调用该子类对象的start()方法。

下面在main中创建了一个新的线程用于遍历0——50的偶数

package com.hedong;

//自定义一个子类继承Thread类

class FirstThread extends Thread{

//重写run方法

@Override

public void run() {

//遍历输出0——50的偶数

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

/**

  • @author hedong

  • @version 1.0

  • @date 2020/4/8 8:21

*/

public class MyThread {

public static void main(String[] args) {

//创建子类对象,即一个线程对象

FirstThread firstThread=new FirstThread();

//调用线程对相爱那个的start方法

firstThread.start();

}

}

注意:

  1. 想要启动多线程,必须调用start()方法。如果手动将调用start()方法的地方改为调用run()方法,那么程序将不会启动多线程模式。

  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU****调度决定。

  3. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出****异常“IllegalThreadStateException”。

四、用实现Runnable接口的方式创建多线程

步骤:

  1. 定义一个子类实现Runnable接口。

  2. 子类中重写Runnable的run()方法。

  3. 在主线程中new一个子类对象。

  4. 将子类对象作为实际参数传递给Thread类的含参构造器中创建线程对象。

  5. 调用该线程对象的start()方法。

package com.hedong;

//自定义一个子类实现Runnable接口

class FirstThread implements Runnable{

//重写run方法

@Override

public void run() {

//遍历输出0——50的偶数

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

/**

  • @author hedong

  • @version 1.0

  • @date 2020/4/8 8:21

*/

public class MyThread {

public static void main(String[] args) {

//创建子类对象

FirstThread firstThread=new FirstThread();

//将子类对象作为一个参数放入Thread的含参构造器中,生成一个线程对象

Thread myThread=new Thread(firstThread);

//调用线程对象的start方法

myThread.start();

}

}

五、 继承方式和实现方式的联系与区别

联系:通过源码可以发现Thread类实际上也实现了Runnable接口。

区别:继承Thread类的方式是将线程代码放在Thread子类的run()方法中。而实现Runnable接口的方式是将线程代码放在Runnable接口子类的run()方法中。

实现Runnable接口的方式的优点:用此种方式实现的多个线程可以共享实现Runnable接口子类中定义的对象。简单来说就是多个线程共用同一份数据资源。

六、线程的生命周期

七、线程安全问题

**问题举例:**假如卡里原先有3000元,A、B两人同时取这张卡里的钱,两人的操作视作两个不同的线程。A打算取2000元,线程A进入if判断3000>2000后发生阻塞,此时B打算也取2000元,并且线程B在线程A发生阻塞的时候顺利取走了2000元,此时卡里只剩1000元,等线程A阻塞结束后继续扣去卡里2000元,这时悲剧发生:卡里最终金额为-1000。

问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式: 同步机制。

八、线程安全问题的解决——Synchronized同步机制

Java对于多线程的安全问题提供了专业的解决方式:同步机制。同步机制可以分为同步代码块和同步方法。

**1、同步代码块:**代码如下,同步监视器相当于一把锁,这个锁可以为任意的类对象, 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 若多线程是用实现Runnable接口的方式实现,则考虑使用this充当对象,若多线程是用继承Thread类的方式实现,则考虑使用当前类充当对象。其中需要被同步的代码即为run()方法中的代码。注意:多个线程必须使用同一把锁,即同一个对象。

synchronized (同步监视器){

// 需要被同步的代码;

}

  • 同步代码块处理实现Runnable接口方式的线程安全的问题:

//自定义一个子类实现Runnable接口

class FirstThread implements Runnable{

//重写run方法

@Override

public void run() {

//遍历输出0——50的偶数

synchronized(this) {

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

}

  • 同步代码块处理继承Thread类方式的线程安全的问题:

//自定义一个子类继承Thread类

class FirstThread extends Thread{

//重写run方法

@Override

public void run() {

synchronized (FirstThread.class){//

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

}

**2、同步方法:**synchronized还可以放在方法声明中,表示整个方法为同步方法。同步方法仍然涉及到同步监视器,只是不用我们自己去声明,当为非静态的同步方法时,同步监视器是this;当为静态的同步方法时,同步监视器是当前类。

  • 同步方法处理实现Runnable接口方式的线程安全的问题:

//自定义一个子类实现Runnable接口

class FirstThread implements Runnable{

//重写run方法

@Override

public void run() {

show();

}

private synchronized void show(){//此时同步监视器为:this

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

  • 同步方法处理继承Thread类方式的线程安全的问题:

//自定义一个子类继承Thread类

class FirstThread extends Thread{

//重写run方法

@Override

public void run() {

show();

}

private static synchronized void show(){//静态方法,此时同步监视器为当前类:FirstThread.class

for (int i = 0; i < 50; i++) {

if(i % 2 == 0){

System.out.println(i);

}

}

}

}

九、线程安全的单例模式之懒汉式

**单例模式:**保证类在内存中只能有一个对象。单例模式分为 懒汉式饿汉式

  • **懒汉式:**默认不会实例化,什么时候用什么时候创建对象。

  • **饿汉式:**类加载的时候就实例化,并且创建单例对象。

懒汉式

public class Lazy {

private static Lazy lazy;

public static Lazy getInstance(){

//用的时候才去创建

if(lazy == null){

lazy = new Lazy();

}

return lazy;

}

}

饿汉式

public class Hungry{

//私有化构造器

private Hungry(){}

//类加载的时候就实例化,并且创建单例对象

private static final Hungry hungry=new Hungry();

public static Hungry getInstance(){

return hungry;

}

}

线程安全问题:

  • **饿汉式线程安全 :**在线程还没出现之前就已经实例化了,因此饿汉式线程一定是安全的。

  • **懒汉式线程不安全:**因为懒汉式是用的时候才创建,这时可能会发生这种情况:线程A进入getInstance()方法发现没有lazy对象,于是准备创建,而在此时线程B也进入了getInstance()方法,也发现没有lazy对象,于是也创建了一个lazy对象,这样A、B两个线程就创建了两个不同的lazy对象,这就不满足我们的单例模式的要求,因此说线程不安全。

同步机制将懒汉式优化为线程安全的单例模式!!!

优化一:效率稍低,每个线程都需要等着前面的线程释放锁之后才能进去拿着对象出来。

public class Lazy {

private static Lazy lazy;

public static synchronized Lazy getInstance(){//静态方法,此时同步监视器为当前类:Lazy.class

if(lazy == null){

lazy = new Lazy();

}

return lazy;

}

}

优化二:效率高,越靠后的线程越不易等待,前面的线程已经创建好了对象之后,后面的线程只需要在最外层判断一下是否有对象即可,若对象存在的话直接就可以拿着对象走了。

public class Lazy {

private static Lazy lazy;

public static Lazy getInstance(){

if(lazy == null){

synchronized (Lazy.class) {

if(lazy == null)

lazy = new Lazy();

}

}

return lazy;

}

}

十、死锁问题

死锁: 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

**死锁的简单示例:**如下面的死锁代码样例,线程一拿到a锁后发生阻塞,此时线程二拿到b锁发生阻塞,当两个线程阻塞结束后,线程一需要继续拿到b锁,而此时b锁在线程二手里,同样,线程此时需要继续拿到a锁,但a锁在线程一手上,双方这时都占用着对方需要的同步资源不放弃,发生死锁现象。

package com.hedong;

/**

  • @author hedong

  • @version 1.0

  • @date 2020/4/8 15:05

*/

public class DeadLock {

public static String a = “a”;

public static String b = “b”;

public static void main(String[] args){

Thread a = new Thread(new MyThread1());

Thread b = new Thread(new MyThread2());

a.start();

b.start();

}

}

class MyThread1 implements Runnable{

@Override

public void run(){

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值