多线程

【一】进程、线程

1.进程:是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,进程一般由程序、数据集合和进程控制块三部分组成。程序是描述进程要完成的功能是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。

进程具有的特征:

动态性:进程是程序的一次执行过程,是临时的,有生命周期,是动态产生,动态消亡的。

并发性:任何进程都可以同其他进程一起并发执行。

独立性:进程是系统进行资源分配和调度的一个独立单位。

结构性:进程由程序、数据、进程控制块三部分组成。

2.线程:是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。

一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(即进程的内存空间)。一个标准的线程由线程ID、当前指令指针PC、寄存器和堆栈组成。

3.区别

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。
  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。
  3. 进程之间相互独立,但一个进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程的资源,某进程内的线程在其他进程不可见。
  4.  调度和切换:线程上下文切换比进程上下文切换要快得多。

4.为什么不使用多进程而是使用多线程?

线程廉价、线程启动较快、退出较快,对系统资源的冲击也较小。而线程彼此分享了大部分核心对象的拥有权。

【二】线程的创建

1.方式一:继承Thread类

步骤:

  1. 定义继承Thread的类
  2. 重写Thread类中的run方法(用于存储线程要执行的代码)
  3. 调用线程的启动方法start()(作用:启动线程、调用run方法)

线程都有自己默认的名称,Thread-编号    该编号从0开始;

currentThread():获取当前对象;

getName():获取当前对象的名称。

2.方式二:实现Runnable 接口

步骤:

  1. 定义类实现Runnable接口
  2. 覆盖Runnable中的run()方法(存放线程要运行的代码)
  3. 通过Thread类建立线程
  4. 将Runnable接口的子类对象作为实际参数传给Thread类的构造函数
  5. 调用Thread类的start()方法开启线程并调用Runnable接口子类的run()方法。

举例:售票测试

实现和继承方式的区别:

                        继承Thread:线程代码存放在Thread子类的run方法中;

                        实现Runnable:线程存放在接口的子类的run方法中;

实现方式好处:避免单继承的局限性;

                          在定义线程,建议使用实现方式;

Thread适合多个线程分别执行多个对象;Runnable适合多个线程同时执行一个对象。

【三】多线程的安全问题

问题:多线程的运行出现了安全问题,比如4个售票窗口共售100张票,可能会出现打印0,-1,-2或出现两张一样的票的情况。

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

解决方法:

同步代码块 :

synchronized(对象){

        需要被同步执行的代码;

 同步的前提:1.必须要有两个或者两个以上的线程;2.必须是多个线程使用同一个锁;

【四】同步

同步的三种表现形式:

1.同步代码块:

synchronized(obj){

}

2.同步函数

public synchronized void method(){

}

3.静态同步函数

public static synchronized void method(){

}

同步代码块使用的是哪一个锁?

同步代码块使用的是任意对象的锁,可以通过Object obj=new Object();创建对象,然后将对象同步锁即可。即为synchronized(obj);

同步函数使用的是哪一个锁?

函数需要被对象调用,那么函数都由一个所属对象引用,就是this,所以同步函数使用的锁是this。即为synchronized(this);

如果同步函数被静态修饰后,使用的锁是什么?

静态的同步方法使用的锁是该方法所在类的字节码文件,即类名.class.即为synchronized(类名.class);

【五】多线程下的单例模式

单例模式:懒汉式和饿汉式
饿汉式一开始就创建对象,所以不存在什么安全隐患。
相比之下,懒汉式会存在一定的多线程安全隐患。
它存在共享数据,对象single

public class Single {
 
   //饿汉式
   private static final Single single=new Single();
   private Single(){}
   public Single getInstance(){
       return single;
   }
}
public class Single {
    //懒汉式;
    private static  Single single=null;
    private Single(){}
    public Single getInstance(){
        if(s==null){
            single=new Single();
        }
        return single;
        
    }

}

修正过后的懒汉式:

public class Single {

    //懒汉式存在线程安全问题
    private static Single single=null;
    private Single(){}
    public static Single getInstance(){
        if(single==null){//提高效率,双重判断
            synchronized(Single.class){
                if(single==null)
                    single=new Single();
            }
        }
        return single;
    }   
}

【六】死锁

产生死锁的根本原因是两个或者两个以上线程在执行过程中,因抢占资源而产生相互等待的一种现象,在申请锁的时候发生了交叉闭环申请。
死锁产生的四个条件:
1.互斥。共享资源同时只能被一个线程访问。
2.占有且等待。线程1在去得共享资源A的时候,请求等待资源B的时候不释放资源A.
3.不可抢占。其他线程不能强行抢占线程的资源。
4.循环等待条件。线程1在持有资源A,同时在等待请求获取资源B。线程2在持有资源B,同时在请求等待线程1持有的资源,形成了交叉闭环。

处理死锁的方法可有以下4种:

1)死锁预防。由于操作系统本身所具有的特点,互斥这个条件无法避免。对死锁产生的其他三个条件进行破坏。首先条件2,占有且等待,可以一次性申请所有的资源,可以破坏掉占有且等待。条件三不可抢占,当线程去请求其他资源时,如果获取不到锁,可以主动释放自己的锁,这样不可抢占的条件也被破坏掉了。条件四循环等待条件,可以对申请的资源进行编号,按序访问,这样线性的去申请资源,则不会造成交叉循环。

2)死锁避免。在资源动态分配的过程中,用某种方法判断防止进入不安全状态。从而避免发生死锁。可以使用银行家算法。死锁避免的算法会导致系统开销的增加。

3)死锁检测。死锁预防和死锁检测都是死锁发生之前的预防策略。死锁检测是通过系统设置的检测机构及时的判断当前系统是否处于死锁状态,并精确的确定当前死锁相关的进程和资源,执行死锁解除策略。

4)死锁解除。这是与死锁检测结合使用的。它使用的方式是剥夺。就是讲进程所占有的资源强行收回,分配给其他进程。

public class Test implements Runnable{
    //private int num=100;
    private boolean flag=true;
    
    Test(boolean flag){
        this.flag=flag;
    }
    
    public void run(){
       
            if(flag){
                while(true){
                    synchronized (MyLock.locka){
                        System.out.println(Thread.currentThread().getName()+"if...locka");
                        synchronized (MyLock.lockb){
                            System.out.println(Thread.currentThread().getName()+"if...lockb");
                        }
                    }
                }
                

            }else{
                while(true){
                    synchronized (MyLock.lockb){
                        System.out.println(Thread.currentThread().getName()+"else...lockb");
                        synchronized (MyLock.locka){
                            System.out.println(Thread.currentThread().getName()+"else...locka");
                        }
                    }
                }
            }
        }
    
}


public class MyLock {
    public static final  Object locka=new Object();
    public static final Object lockb=new Object();

}


public class DeadLockTest {
    public static void main(String[] args) {
        Test a=new Test(true);
        Test b=new Test(false);
        
        Thread t1=new Thread(a);
        Thread t2=new Thread(b);
        t1.start();
        t2.start();
    }
}

其中的一种输出:

Thread-0if...locka
Thread-1else...lockb

讲解:

线程0(即线程a)首先抢到资源,进入run方法,拿到锁,执行Treas-0if...locka,后释放资源。
线程1(即线程b)抢到资源,进入run方法,拿到锁,执行Thread-1else...lockb,后释放资源。
线程0抢到资源,执行synchronized (MyLock.lockb),但是线程1中并未释放锁,所以不能仅需进行。。。
同理,线程1也不能进行,故产生了死锁现象。

【七】多线程间的通信

1.举例:现有两个线程:Input、Output;共同对资源Resource进行处理。 

 

/**
 * 多线程之间的通信
 * 资源
 */
public class Resource {
    String name;
    String sex;
}

/**
 * 多线程之间的通信
 * 输入
 */
public class Input implements Runnable{
    Resource r;
   
    Input(Resource  r){
        this.r=r;
    }
    
    public void run(){
        int x=0;
        while(true){
            synchronized (Resource.class) {
                if (x == 0) {

                    r.name = "zhangsan";
                    r.sex = "nan";
                } else {
                    
                        r.name = "小红";
                        r.sex = "女女女女";
                    
                } 
            }
            x++;
            x = x % 2;
           
        }
    }
}

/**
 * 多线程之间的通信
 * 输出
 */
public class Output implements Runnable{
    Resource r;
    Output(Resource r){
        this.r=r;
    }
    public void run(){
       
        while(true){
            synchronized (Resource.class){
                System.out.println(Thread.currentThread().getName()+":"+r.name+","+r.sex);
            }
        }
        
    }
}

/**
测试
 */
public class ResourceTest {
    public static void main(String[] args) {
        Resource r=new Resource();
        Input in=new Input(r);
        Output out=new Output(r);
        
        Thread t1=new Thread(in);
        Thread t2=new Thread(out);
        
        t1.start();
        t2.start();
    }
}

输出部分截图:

2.等待唤醒机制

wait():等待;将正在执行的线程释放其执行资格和执行权,并存储到线程池中。

notify():唤醒;唤醒线程池中被wait()的线程,一次唤醒一个线程,而且任意的。

notifyAll():唤醒所有等待机制。可以将线程池中所有wait()的线程唤醒。

上述方法都使用在同步中,对持有监视器(锁)的线程进行操作。

举例:多生产者多消费者。(多线程)

/**
 * 多线程生产者消费者
 * 资源
 */
public class Resource3{
    private String name;//资源名
    private int id;//资源代号
    boolean flag=false;
    
    //设置资源
    public synchronized void getIn(String name){
        while(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name=name+":"+(id++);
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
        flag=true;
        this.notifyAll();
    }
    
    public synchronized void out(){
        while(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
        flag=false;
        this.notifyAll();
    }
}

/**
 * 多线程生产者消费者
 * 生产者
 */
public class Producer  implements Runnable{
    Resource3 resource;
    
    Producer(Resource3 resource){
        this.resource=resource;
    }
    
    public void run(){
        while(true){
            resource.getIn("烤鸭");
        }
    }
}

/**
 * 多线程生产者消费者
 * 消费者
 */
public class Consumer  implements Runnable{
    Resource3 resource;

    Consumer(Resource3 resource){
        this.resource=resource;
    }

    public void run(){
        while(true){
            resource.out();
        }
    }
}

/**
 * 多线程生产者消费者
 * 测试
 */
public class ResourceTest3 {
    public static void main(String[] args) {
        Resource3 resource=new Resource3();
        Producer producer=new Producer(resource);
        Consumer consumer=new Consumer(resource);
        
        Thread t1=new Thread(producer);
        Thread t2=new Thread(producer);
        Thread t3=new Thread(consumer);
        Thread t4=new Thread(consumer);
        
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

其中一部分输出截图:

 

3.JDK1.5后的新特性:

  同步代码块对于锁的操作是隐式的,而1.5使用封装对象的思想,出现了显式锁Lock。

Lock接口的出现,替代了同步代码块和同步函数,并且更为灵活,一个锁上可以加上监视器对象。

Lock lock=new ReentrantLock();

lock.lock();//获取锁

lock.unlock().;//释放锁,通常需要定义在finally代码块中

Condition接口的出现,替代了Object的wait()、notify()、notifyAll()方法。

lock.newCondition():通过已有的锁获取一个监视器对象。

await():等待。

signal():释放一个线程。

signalAll():释放所有线程。

举例多线程生产者消费者 JDK1.5版本(多个监视器):

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多线程生产者消费者
 * 资源
 * JDK1.5版本改写
 * * */
public class Resource4 {
    private String name;
    private int id=1;
    boolean flag=false;
    //创建锁
    Lock lock=new ReentrantLock();
    //创建监听器对象
    Condition producer_con=lock.newCondition();//监视生产者
    Condition consumer_con=lock.newCondition();//监视消费者
    
    
    public void getIn(String name){
        
        lock.lock();
        try{
            while(flag){
                try {
                    producer_con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
            this.name=name+":"+(id++);
            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
            flag=true;
            consumer_con.signal();
        }finally {
            lock.unlock();
        }
        
    }

    public void out(){
        lock.lock();
        try{
            while(!flag){
                try {
                    consumer_con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
            flag=false;
            producer_con.signal();
        }finally {
            lock.unlock();
        }

    }
}


/**
 * 多线程生产者消费者
 * 生产者
 * JDK1.5版本
 */
public class Producer2 implements Runnable{
    private Resource4 resource4;
    Producer2(Resource4 resource4){
        this.resource4=resource4;
    }
    public void run(){
        while(true){
            resource4.getIn("包子");
        }
        
    }
}


/**
 * 多线程生产者消费者
 * 消费者
 * JDK1.5版本
 */
public class Consumer2 implements Runnable{
    private Resource4 resource4;
    Consumer2(Resource4 resource4){
        this.resource4=resource4;
    }
    public void run(){
        while(true){
            resource4.out();
        }
       
    }
}

/**
 * 多线程生产者消费者
 * 测试
 * JDK1.5版本
 */
public class ResourceTest4 {
    public static void main(String[] args) {
        Resource4 resource4=new Resource4();
        Producer2 producer2=new Producer2(resource4);
        Consumer2 consumer2=new Consumer2(resource4);
        
        Thread t1=new Thread(producer2);
        Thread t2=new Thread(producer2);
        Thread t3=new Thread(consumer2);
        Thread t4=new Thread(consumer2);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

4.wait()和sleep()方法的区别

  • wait()方法可以设置等待时间,可以不设置;sleep()方法必须设置时间;
  • wait()方法会需要释放锁、释放CPU执行权;sleep()方法释放CPU执行权;

【八】停止线程

1.stop()方法

2.run()方法结束

线程类的其他方法:

interrupt()方法可以使线程从冻结状态强制恢复为运行状态,会抛出一个InterruptedException异常,让线程具备cpu的执行资格。

setDeamon()设置守护线程,守护线程和一般线程运行方式一样都会抢占CPU执行权,不一样的是结束,一般线程都会手动设置结束,而守护线程是只要其他线程结束后,无论守护线程处于何种状态,都会结束。当所有线程都是守护线程时,java虚拟机会自动退出(即程序会自动退出)。

join()方法:临时加入一个线程,意思就是在运行的时候,先运行加入进来的这个线程,然后其他线程再相互抢占CPU执行权。

yield():暂停当前正在进行的线程,执行其他线程。

toString():获取当前线程的字符串。如图所示。优先级指的是能获取CPU执行权的几率,优先级越大几率越大。

 【九】典型问题

问题一:

public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run");
            }
        }){
            public void run(){
                System.out.println("thread run");
            }
        }.start();
    }

上述运行结果是thread run。 本应该运行任务(即Runnable中的run()方法),但是Thread其子类中的run()方法重写了,所以运行子类中的run()方法。

问题二:

public class Method implements Runnable{
    public void run(Thread t){
        //...
    }
}

上述问题会报错,因为Method实现了Runnable但是并未重写其run()方法,所以Method类是抽象类,其应包含抽象方法。所以出问题的是第一行。Method类中的run方法并不是重写的方法,是run()方法的重载形式。

【十】线程池

1.概念:线程池在系统启动是创建大量空闲的线程,程序将任务传给线程值,线程池会启动一条线程来执行任务,执行结束后,该线程不会死亡,会返回线程池中称为空闲状态,等待下一个任务。

主要参数:

public ThreadPoolExecutor(int corePoolSize, int maxmumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue){
      this(corePoolSize,maxmumPoolSize,keepAliveTime,unit,workQueue,Executors.defaultThreadFactory(),defaultHandler)         
    }
  •  corePoolSize:线程池大小,当向线程池提交一个任务的时候,如果线程池已经创建的线程数小于corePoolSize,即使线程池中存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize。
  • maximumPoolSize:线程池最大大小,线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。
  • keepAliveTime:线程存活保持时间,线程的空闲时间如果超过存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于核心线程数。
  • workQueue:任务队列,用于传输和保存等待执行任务的阻塞队列。
  • threadFactory:线程工厂,用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
  • handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

2.工作机制

  1. 在线程池的编程模式下,任务提交给整个线程池,线程池接收到任务后,就在内部寻找空闲线程,如果有空闲线程,就将任务交给某个空闲的线程。
  2. 一个线程一次性只能执行一个任务,但是可以同时向一个线程池中提交多个任务。

3.线程池的优势

  • 降低系统资源消耗,通过重用已存在的线程,降低了线程创建和销毁造成的消耗。
  • 提高系统响应速度,无需等待新线程的创建,便能立即执行任务。
  • 方便线程并发数的管控。线程若是无限制的创建,可能会导致内存占用过多而产生内存溢出,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
  • 提供其他强大的功能,延时定时线程池。

4.线程池的处理流程

 5.四种常见的线程池

CachedThreadPool:可缓存的线程池,没有核心线程,非核心线程的数量为Integer.max_value无限大,当有需要时创建线程来执行任务,没有需要时回收线程。适用于耗时少,任务量大的情况。

ScheduledThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程。适用于负载较重的场景,对当前线程数量进行限制。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值