多线程(Thread)

一、多线程的基础概念

首先要知道什么是进程,什么是线程。
进程:正在运行的程序,当一个程序进入内存运行,就是一个进程。

线程:包含在进程中,一个进程至少由1个线程组成。
线程和进程关系图

多线程

需要知道2个概念,串行和并行。以下是图例
串行:线程依次执行,A执行完了,B执行,然后再C执行
并行:A、B、C同时执行
串行和并行
并发产生的问题:

  • 线程安全问题:多个线程同时操作同一个变量时,会出现数据不准确问题,例如,对象A银行卡账户有100元,线程A和线程B同时对其操作,线程A操作取出了10元,对象A余额还有90;线程B存入20元,余额还有120了,这就出现了数据不一致的问题。

如何避免线程安全问题:

  • 同步代码块
  • 同步方法
  • lock锁

二、如何实现多线程?

在java中,实现多线程有3个方法

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

1、继承Thread类

package com.wenan.stdthread;

/**
 * 描述:   继承Thread类,实现一个自定义线程
 */
public class Mythread extends Thread{

    private String name;

    Mythread(String name) {
        this.name = name;
    }

    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(name+"正在运行"+i);
            try {
                // 随机暂停时间,以查看差异
                sleep((int)Math.random()*100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试

public class ThreadDemo {
    public static void main(String[] args) {
        Mythread thread1 = new Mythread("thread1");
        Mythread thread2 = new Mythread("thread2");
        thread1.start();
        thread2.start();
    }
}

运行结果

···
thread2正在运行7
thread1正在运行15
thread2正在运行8
thread1正在运行16
thread2正在运行9
···

在线程内部随机暂停时间,可以发现,线程1和线程2是交替运行。

2、实现Runnable接口

package com.wenan.stdthread;

/**
 * 描述:    MyRunnable
 */
public class MyRunnable implements Runnable {
    private String name;

    MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(name + "正在运行" + i);
            try {
                // 随机暂停时间,以查看差异
                Thread.sleep((int) (Math.random() * 100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

测试

public class ThreadDemo {
    public static void main(String[] args) {
        // 通过继承Thread类实现自定义线程
        Mythread thread1 = new Mythread("thread1");
        Mythread thread2 = new Mythread("thread2");
        thread1.start();
        thread2.start();

        // 通过实现Runnable方法实现多线程
        MyRunnable thread3 = new MyRunnable("thread3");
        MyRunnable thread4 = new MyRunnable("thread4");
        new Thread(thread3).start();
        new Thread(thread4).start();
    }
}

结果

···
thread4正在运行26
thread2正在运行32
thread3正在运行30
thread3正在运行31
thread1正在运行35
thread4正在运行27
thread2正在运行33
···

3、通过Thread实现和Runnable实现的区别

Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的。

因此Thread和Runnable的主要区别就是:

  • Runnable是一个接口,Thread是一个实现了Runnable接口的类。
  • 通过Thread实现的自定义run方法,每次执行时都需要新建一个线程对象,不方便使用,而通过实现Runnable接口的run方法,可以重复在多个线程使用

三、解决线程安全问题

  • synchronized

格式:

	synchronized (锁对象) {
		可能会出现线程安全问题的代码(访问了共享数据的代码)
	}
	或者
	修饰符 synchronized 返回值类型 方法名称(参数列表){
			可能会出现线程安全问题的代码(访问了共享数据的代码)	
	}

先看一个线程不安全的情况


public class MyThread implements Runnable {
    // 线程共享的对象
    private Integer money = 100;

    // 对象名称
    private String name;

    MyThread(String name) {
        this.name = name;
    }


    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (money > 0) {
                money = money - 10;
                System.out.println(name+"取出了10元,还剩"+money+"元");
            }
        }
    }
}

测试

public class sychronizedDemo {
    public static void main(String[] args) {
        MyThread wangxiaoer = new MyThread("王小二");
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
    }
}

输出结果

王小二取出了10元,还剩80元
王小二取出了10元,还剩80元
王小二取出了10元,还剩60元
王小二取出了10元,还剩70元
王小二取出了10元,还剩40元
王小二取出了10元,还剩30元
王小二取出了10元,还剩20元
王小二取出了10元,还剩10元
王小二取出了10元,还剩50元
王小二取出了10元,还剩0元

上面可以看到,王小二第一次取钱的时候,只取了10元,但钱包只剩下80元,第二次又取了10元,钱包还有 80元。这就产生了数据不一致的问题。

  • 使用synchronized对方法进行同步
public class MyThread implements Runnable {
    // 线程共享的对象
    private Integer money = 100;

    // 对象名称
    private String name;

    MyThread(String name) {
        this.name = name;
    }


    @Override
    public /*synchronized*/ void run() {
        synchronized(/*this*/Mythread.class) {
            for (int i = 0; i < 4; i++) {
                if (money > 0) {
                    int take=10;
                    money = money - take;
                    System.out.println(Thread.currentThread().getName()+name + "取出了"+take+"元,还剩" + money + "元");
                }
            }
        }
    }

    public static void main(String[] args) {
        MyThread wangxiaoer = new MyThread("王小二");
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
    }
}

执行结果,线程逐次执行

Thread-0王小二取出了10元,还剩90元
Thread-0王小二取出了10元,还剩80元
Thread-0王小二取出了10元,还剩70元
Thread-0王小二取出了10元,还剩60元
Thread-2王小二取出了10元,还剩50元
Thread-2王小二取出了10元,还剩40元
Thread-2王小二取出了10元,还剩30元
Thread-2王小二取出了10元,还剩20元
Thread-1王小二取出了10元,还剩10元
Thread-1王小二取出了10元,还剩0元
  • 使用lock进行加锁
public class Mythread2 implements Runnable {
    Lock lock = new ReentrantLock();
    // 线程共享的对象
    private Integer money = 100;
    // 对象名称
    private String name;

    Mythread2(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Mythread2 wangxiaoer = new Mythread2("王小二");
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
        new Thread(wangxiaoer).start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            lock.lock();
            try {
                if (money > 0) {
                    int take = 10;
                    money = money - take;
                    System.out.println(Thread.currentThread().getName()+"第"+i+"次执行:" + name + "取出了" + take + "元,还剩" + money + "元");
                }else  {
                    int take = 5;
                    money = money + take;
                    System.out.println(Thread.currentThread().getName()+"第"+i+"次执行:" +name + "存入了" + take + "元,还剩" + money + "元");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

输出结果

Thread-0第0次执行:王小二取出了10元,还剩90元
Thread-0第1次执行:王小二取出了10元,还剩80元
Thread-0第2次执行:王小二取出了10元,还剩70元
Thread-0第3次执行:王小二取出了10元,还剩60元
Thread-0第4次执行:王小二取出了10元,还剩50元
Thread-0第5次执行:王小二取出了10元,还剩40元
Thread-0第6次执行:王小二取出了10元,还剩30元
Thread-0第7次执行:王小二取出了10元,还剩20元
Thread-0第8次执行:王小二取出了10元,还剩10元
Thread-0第9次执行:王小二取出了10元,还剩0元
Thread-0第10次执行:王小二存入了5元,还剩5元
Thread-0第11次执行:王小二取出了10元,还剩-5元
Thread-0第12次执行:王小二存入了5元,还剩0元
Thread-0第13次执行:王小二存入了5元,还剩5元
。。。
  • Lock与synchronized 的区别
维度synchronizelock
存在层次上Java的关键字,在jvm层面上是一个接口
锁的释放1、获取锁的线程执行完,自动释放锁 2、线程发生异常在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)
死锁的产生在发生异常时候会自动释放占有的锁,因此不会出现死锁发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
锁的状态无法判断可以判断,通过tryLock判断
锁的类型可重入 不可中断 非公平可重入 可判断 可公平(两者皆可)
性能少量同步大量同步
锁的调度使用Object对象本身的wait 、notify、notifyAll调度机制可以使用Condition进行线程之间的调度
用法在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

四、线程的生命周期和5种状态

  • 新建状态(New):在new Thread()的时候,就新建了一个线程。
  • 就绪状态(Runnable):调用start()方法时,线程进入就绪状态,等待cpu调度,cpu调度就进入了运行状态。
  • ** 运行状态(Running)**:当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
  • 阻塞状态(Blocked):处于运行状态的线程出于某种原因而暂时放弃CPU使用权,进入阻塞状态。
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
    线程的5种状态

五、线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。取值范围是1~10;

  • Thread.MAX_PRIORITY :优先级是10
  • Thread.NORM_PRIORITY:优先级是5
  • Thread.MIN_PRIORITY:优先级是1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值