一、基本概念
线程:是依赖于进程的执行绪(执行路径/控制单元),是程序使用CPU的基本单位。
进程:当前正在执行的程序,代表一个应用程序在内存中的执行区域。
多进程:同一时间段内执行多个任务。同一时刻只能执行一个任务。如Windows为代表的操作系统。
多进程并不提高某个程序的执行速度,仅仅是提高了CPU的使用率。真正的多进程执行是指多核同时计算。
单线程:一个进程中,只有一个线程执行。
多线程:同一个进程中,多个线程执行。这多个线程共享该进程资源(堆内存与方法区),栈内存独立,即每一个线程占用一个栈。
线程两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个。(线程随机性)
Java使用的为抢占调度模型。
线程并行与线程并发
线程并行:正常的多线程执行就是线程并行。即逻辑上同一时间同时运行。
线程并发(异常):由于线程抢占而不应出现的某一时刻的线程及相关数据状态。如并发修改异常的产生。
JVM的启动支持多线程:
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。
线程生命周期
二、线程创建方式
线程创建方式一:
继承Thread类.要覆盖其run方法,调用线程的start方法.
作用:1.启动线程
2.运行run方法。目的是将自定义的代码存储在run方法中,让线程运行
cpu每次只执行一个程序,只是在快速的不同线程间切换,表现了多线程的随机性
- class demo extends Thread{
- public void run(){
- }
- }
run方法用于存储线程要运行的代码。
demo demo=new demo();创建对象就创建了一个线程。
run方法和 start方法
run方法 仅仅是对象调用方法,并没有运行线程
start方法 是开启线程并且执行线程中的run方法
创建线程的第二种方式:实现Runnable接口。
创建线程
Thread t=new Thread(new 对象名());
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
实现方式和继承方式有什么区别?
继承Thread类:线程代码块存放在Thread子类的run方法中
实现Runnable,线程代码存放在接口的子类的run方法中,可以被多实现。
继承方式有局限性。要被实现多线程的一个类 如果继承了父类 就不能再继承Thread类。
实现方式就变面了单继承的局限性。
方式一与方式二的区别
方式一:当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程中共享该数据。加上静态,虽然实现了共享但是生命周期过长。
方式一:如果一个类明确了自己的父类,那么它就不可以再继承Thread。因为java不允许类的多继承。
方式二:将线程与运行的业务逻辑分离,可以让多个线程共享业务逻辑中的数据。
方式二:可以让业务类不再继承Thread而专注于业务继承其他类,避免了单继承的局限性。
三、多线程——Lock锁简介
相较于synchronized方式,Lock锁的出现使同步操作更为灵活。无需使用限制性强的代码块。
Lock同样为抽象类,需要使用其子类ReentrantLock的对象完成方法调用。
主要方法:
public void lock()获取锁
public void unlock() 释放锁
在多线程的代码编辑过程中,由于考虑得不够周全,会出现死锁的情况。
线程死锁代码:备注
原因分析:
线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码,但是此时,两个锁均处于锁死状态。最终导致两线程相互等待,进入无限等待状态。
有同步代码块的嵌套动作。
解决方法:
不要写同步代码块嵌套。
示例代码
- private Lock lock=new ReentrantLock();
- private Condition condition =lock.newCondition();
- public void cet(String name ) throws
- {
- lock.lock();
- try
- {
- while(flag)
- contidition.await();
- this.name=name+"--"+count++;
- sop(Thread.currentThread().getName()+"...生产者..."+this.name)
- flag=true;
- condition.signalAll();
- }
- finally
- {
- lock.unlock();
- }
- }
四、多线程通信
线程间通信:多个线程在操作同一个资源,但是操作的动作不同。
1.是不是两个或两个以上的线程。解决办法 两个线程都要被同步。
2.是不是同一个锁。解决办法 找同一个对象作为锁。
等待唤醒机制。
wait后,线程就会存在线程池中,notify后就会将线程池中的线程唤醒。
notifyAll();唤醒线程池中所有的线程。
实现方法 :
给资源加个标记 flag
synchronized(r)
- {
- while(r.flag)//多个生产者和消费者 if(r.flag)//一个生产者和消费者
- r.wait();
- 代码
- r.flag=true;
- r.notify();
- r.notifyAll();
- }
例子:
- package cn.itcast.thread;
- /*
- * 多线程的通信
- * 输入线程赋值
- * 输出线程打印值
- *
- */
- //资源对象
- class Resource{
- String name;
- String sex;
- boolean b = false;//定义,b=true,赋值完成,b = false打印完成
- }
- //定义输入线程
- class Input implements Runnable{
- Resource r ;
- Input(Resource r){this.r = r;}
- public void run(){
- int x = 0 ;
- while(true){
- synchronized(r){
- //输入线程,判断b的值,如果是真,等待
- if(r.b==true)
- try{r.wait();}catch(Exception e){}
- if(x%2==0){
- r.name = "张三";
- r.sex = "男";
- }else{
- r.name = "lisi";
- r.sex = "nv";
- }
- x++;
- //标记改成true
- r.b = true;
- //唤醒输出线程
- r.notify();
- }
- }
- }
- }
- //定义输出线程,打印变量值
- class Output implements Runnable{
- Resource r ;
- Output(Resource r){this.r = r;}
- public void run(){
- while(true){
- synchronized(r){
- if(r.b==false)
- try{r.wait();}catch(Exception e){}
- System.out.println(r.name+"..."+r.sex);
- r.b = false;
- r.notify();
- }
- }
- }
- }
- public class ThreadDemo6 {
- public static void main(String[] args) {
- Resource r = new Resource();
- Input in = new Input(r);
- Output out = new Output(r);
- Thread tin = new Thread(in);
- Thread tout = new Thread(out);
- tin.start();
- tout.start();
- }
- }