JAVA中线程、锁使用及其基本方法

目录


概述

进程:是一个正在执行中的程序,每一个进程的执行都有一个执行顺序,该执行顺序是一个控制单元。是线程的容器。资源调度、分配和管理的最小单位。
线程:是进程中的一个独立的控制单元、运行基本单位(CPU调度的最小单位),线程控制着进程的执行。


线程系列

每一个进程中至少有一个线程,在java中主(main)线程就是一条线程。Java程序每次运行不止有一个main线程,还有如垃圾回收机制的线程

如何实现线程?

  • 根据JavaAPI中所写,有Thread类。

public class Thread extends Object implements Runnable

  • 创建一个新的执行线程有两种方法。

    一、将一个类声明为Thread的子类,这个子类应该重写run类的方法Thread
    二、创建一个线程是声明实现类Runnable接口。那个类然后实现了run方法。然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

//方法一
class PrimeThread extends Thread{
    public void run(){
        //需要线程执行的代码
    }
}
public static void main(String[] args){
    PrimeThread p = new PrimeTherad();
    p.start();
}
//方法二
class PrimeRun implements Runnable{
    public void run(){
        //需要线程执行的代码
    }
}
public static void main(String[] args){
    PrimeThread p = new PrimeTherad();
    Thread t = new Thread(p);
    t.start();
    //简写 new Thread(new PrimeThread()).start();
}
  • 为什么要覆盖run()方法?
    Thread类用于描述线程。该类就定义了一个功能,用于存储要线程运行的代码,该存储功能就是run()方法。可以看出main()方法是存放主线程代码的位置。

线程的使用

  • 随机性:
    谁抢到cpu谁执行(多核除外),至于执行多长时间是cpu说了算。一般不会刻意的执行完一条线程再执行另一条。

  • 生命周期:
    线程流程图

  • 两种方法使用的区别
    在上述自定义线程中我们可以知道一共有两种方法可以实现多线程。
    那么这两种有什么区别呢?
    一、避免单继承的局限性:
    二、代码存放的位置不同:
    继承Thread,线程代码存放在Thread子类run方法中。
    实现Runnable,线程代码存放在接口的子类的run方法中。
    因为Runnable只有一个抽象方法run()。

多线程的安全问题

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

  • 解决办法
    对多条操作共享数据的语句,只能让一个线程执行完在执行过程中不可以参与执行。Java提供了同步代码块——synchronized,实现了其原子性。

所以推荐使用Runnable接口实现多线程

停止线程

一般来说开启多线程运行的运行代码是循环结构,所以只要控制循环,就可以让run方法运行结束,线程就结束了,这种是最好的。

  • interrupt()

public void interrupt()
中断这个线程。

一般使用方法:用其他线程去打断处于阻塞(冻结)状态的线程,强制使其恢复带运行状态。这时该被恢复的线程会抛出InterruptException异常,我们就可以在这个catch里面进行代码操作。

  • setDaemon()

public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。

当线程开启守护线程后与普通线程共用CPU资源,在这些过程中是没有区别的。
当所有非守护线程结束后,JVM退出,守护线程会自动结束。

  • join()

public final void join() throws InterruptedException
等待这个线程死亡。

当前线程调用这个方法时,表该线程需要当前CPU的执行权(抢夺性的),拥有者CPU执行权的线程会释放执行权。一般用于临时加入线程。

  • yield

public static void yield()对调度程序的一个暗示,即当前线程愿意分享当前使用的CPU。 调度程序可以自由地忽略这个提示。
以改善否则会过度利用CPU的线程之间的相对进度。 其使用应与详细的分析和基准相结合,以确保其具有预期的效果。

临时释放,稍微缓解线程的执行频率以达到线程能平均占有CPU的效果。

其他常用方法

  • setPriority

public final void setPriority(int newPriority)更改此线程的优先级。

默认的优先级是5,(1-10),优先级越高执行频率越高,哪怕是10也不存在一直完全占有CPU。
默认参数(MIN_PRIORITY,NORM_PRIORITY,MAX_PRIORITY)

  • toString

public String toString() 返回此线程的字符串表示,包括线程的名称,优先级和线程组。

Thread 类中覆盖了Object 中toString 方法建立了特有的字符串表现形式。

  • getName

public final String getName()返回此线程的名称。

使用线程的快速方式

//一
new Thread(){
    public void run(){
        //线程执行代码
    }
}.start();
//二
Thread t = new Runnable(){
    public void run(){
        //线程执行代码
    }
}
new Thread(t).start();

锁系列

锁是用于通过多个线程控制对共享资源的访问的工具。通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。

如何实现锁?

  • 特点:
    对象如同锁,持有锁的线程可以在同步中执行。需要用一个对象锁。 弊端:多个线程需要判断锁,较销毁资源。

  • 同步前提:
    1、两个或者两个线程以上的时候。
    2、多个线程需要使用同一个锁。

  • 同步表现形式
    一、同步代码块。 二、同步函数。

synchronized(对象){
    //需要被同步的代码
}

public synchronized void Demo(){
    //需要被同步的代码
}
  • 是哪一个锁?
    实现一,锁是括号中传入的对象。
    实现二,锁是当前的类是this。
    因为函数需要被对象调用,那么函数都有一个所属引用。就是this。

  • 静态函数是哪一个锁?

public static synchronized void Demo(){
    //需要被同步的代码
}

静态进内存时,没有本类对象,所以没有this。 是由类调用的,所以是该类对应的字节码文件对象 —— 类名.class

死锁

  • 死锁的形成
    当两个或两个以上的进程或线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
synchronized(对象1){
//拥有对象1锁的线程执行到着的时候就会去获取对象2的锁,
    synchronized(对象2){
        //需要被同步的代码
    }
}

synchronized(对象2){
//拥有对象2锁的线程执行到着的时候就会去获取对象1的锁,
    synchronized(对象1){
        //需要被同步的代码
    }
}

线程间的通讯

在多线程的程序中线程之间的通讯是非常重要的

Object-wait and notify and notifyAll

该方法只能由作为该对象的监听器的所有者的线程调用。

  • wait()
 public final void wait()
            throws InterruptedException

导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。
当前的线程必须拥有该对象的监听器。 该线程释放此监听器的所有权,并等待另 一个线程通知等待该对象监听器的线程通过调用notify方法或notifyAll方法 。
然后线程等待,直到它可以重新获得监听器的所有权并恢复执行。

  • 注意点
    一、我们可以知道wait() notify() notifyAll() 只能用于同步中。
    二、wait() 是让持有当前锁上的线程等待并交出锁监听器。
    三、只能让另一个线程执行notify()或notifyAll()才能唤醒wait()中的线程。
    四、在执行的时候可以用 锁对象.wait() 的方式执行。
    五、一个锁就对应了一个Object。

  • wait() 机制
    在同步中,当一个线程执行 锁对象.wait() 的时候 这个线程将会回到线程池中按顺序进行等待唤醒。意思就是说当有第一个线程被 wait() 的时候它回到线程池中排序为第一号,第二个被 wait() 回到线程池就是第二号,所以当另一个线程执行notify的时候首先被唤醒的就是第一号。

  • 为什么定义在Object类中?
    因为这些方法在操作同步中线程时,都必须标识它们所操作的持有锁,只有同一个锁上的被等待的线程,可以被同一个 notify() 唤醒,不可以对不同锁中的线程进行唤醒。

  • 弊端
    在多线程中哪个线程获得cpu是具有随机性的,所以如果我们要处理多线程之间的等待和唤醒的时候,就可能会遇到问题造成所有的线程都进行wait()状态。

  • notify() 和 notifyAll() 区别
    因为存在上述的弊端,所以就出现了notifyAll()它可以唤醒同一个锁上被 wait() 的所有线程。

Lock

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition(特点:可实现一个Lock对应多Condition) 。

  • Condition
    Condition因素将Object监听器方法(wait,notify和notifyAll )放入到不同的对象,以使每个对象具有多个等待集合的效果,通过将它们与任意的Lock实现结合使用。 Lock替换synchronized方法和语句的使用, Condition取代了Object监听器方法的使用。

一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

  • Condition基本使用
    void await() 导致当前线程等到发信号或 interrupted 。
    signal() 唤醒一个等待线程。
    signalAll() 唤醒所有等待线程。

  • Lock基本使用
    void lock() 获得锁。
    Condition newCondition() 返回一个新Condition绑定到该实例Lock实例。
    void unlock() 释放锁。

  • 注意点
    当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。

  • 用它特有的性质实现生产者-消费者模式

import java.util.concurrent.locks.*;
class ProductConsumerDemo{
    public static void main(String[] args){
        Rescure res = new Rescure();
        new Thread(new Producer(res)).start();
        new Thread(new Consumer(res)).start();
        new Thread(new Producer(res)).start();
        new Thread(new Consumer(res)).start();
    }
}

class Rescure{
    private int count = 1;
    private String name;
    private boolean flag = false;

    private Lock lock = new ReentrantLock();
    private Condition condition_pro = lock.newCondition();
    private Condition condition_con = lock.newCondition();

    public void set(String name)throws InterruptedException{
        lock.lock();
        try{
            while(flag)
                condition_pro.await();
            this.name = name+"---"+count++ ;
            String threadName=Thread.currentThread().getName();
            System.out.println(threadName+"生产"+this.name);
            flag = true;
            condition_con.signal();
        }
        finally{
            lock.unlock();
        }
    }

    public void out()throws InterruptedException{
        lock.lock();
        try{
            while(!flag)
                    condition_con.await();
            String threadName=Thread.currentThread().getName();
            System.out.println(threadName+"消费----"+this.name);
            flag = false;
            condition_pro.signal();
        }
        finally{
            lock.unlock();
        }
    }
}

class Producer implements Runnable
{
    private Rescure res;
    Producer(Rescure res){
        this.res = res;
    }
    public void run(){
        while(true){
            try{
                res.set("商品");
            }catch(Exception e){}
        }
    }
}

class Consumer implements Runnable
{
    private Rescure res;
    Consumer(Rescure res){
        this.res = res;
    }
    public void run(){
        while(true){
            try{
                res.out();
            }catch(Exception e){}
        }
    }
}

猜你喜欢

转载自blog.csdn.net/bfinwr/article/details/79294244