Java多线程技术概述

 介绍多线程之前要介绍线程,介绍线程则离不开进程。

首先 ,

进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;

线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。

多线程:一个进程中不只有一个线程。

一、线程与进程

进程:通俗来解释就是一个程序,一个App,打开任务管理器可以看到当前电脑中正在运行的进程

线程:一个进程中一般包含多个线程,打开任务管理器也可以看到当前电脑中正在运行的线程每个各自执行自己的任务来实现进程的运行,当一个进程中的最后一个线程结束时,整个进程就结束了。

线程的6种状态:

  • NEW(未启动的线程)
  • RUNNABLE(执行的线程)
  • BLOCKED(被阻塞的线程)
  • WAITING(无限期等待的线程)
  • TIMED_WAITING(有限期等待的线程)
  • TERMINATED(已结束的线程)

二、Java中线程创建的三种方式

1.Thread类: 通过创建一个类来继承Thread类,在这个类中重写run()方法,通过这个类创建的对象就是一个线程。

class MyThread extends Thread{
    @Override
    public void run() {
        //执行的Java语句
    }
}
public static void main(String[] args) {
        MyThread t = new MyThread();
        //线程启动
        t.start();
}

2.Runnable接口:通过创建一个类来实现Runnable接口,在这个类中重写run()方法,通过这个类创建的对象是一个线程任务,我们将这个任务传给一个Thread对象即可执行这个线程任务。(推荐使用这种方式,传入一个Runnable任务即可执行线程,跟使用线程池有关)

class MyRunnable implements Runnable{
    @Override
    public void run() {
         //执行的Java语句
    }
}
public static void main(String[] args) {
        MyRunnable r = new MyRunnable1();
        Thread t = new Thread(r);
        t.start();
}

3.Callable接口(很少用的方式):创建方式与Runnable相似。创建此类线程会产生一个返回值,如果主程序要获取这个返回值,则主程序会在Callable线程运行结束后再运行,不获取的话,则两个线程并行。

三、线程中一些常用的方法

Thread的常用方法

       1.String getName()                         //获取该线程的名字
       2.void setName(String name)                //设置该线程的名字
       3.void start()                             //线程开始
       4.static Thread currentThread()            //获取当前线程对象
       5.static void sleep(long millis)           //让当前线程休眠,进入阻塞状态,传入的参数单位为毫秒
       6.void setDaemon(boolean on)               //将线程设置为守护线程或者用户线程
       7.void interrupt()                         //中断此线程
       8.int getPriority()                        //返回此线程的优先级
       9.void setPriority(int newPriority)        //更改此线程的优先级
       10.Thread(Runnable target)                 //(构造方法)传入一个Runnable任务
       11.Thread(String name)                     //(构造方法)为线程取一个名字

1、interrupt方法和stop方法

线程在运行的过程中两种中断线程的方法,一个是stop()方法,一个是interrupt()方法。

Sun公司在JDK1.2版本的时候将stop()方法改为过时了,因为这种中断线程的方式是在外部强制的,这可能会导致在中断过程数据丢失,所以是不安全的。

使用interrupt()方法则是一种安全的方式,当在线程外部调用interrupt()方法时,JVM会给运行的线程传递一个异常对象,当线程接收到异常对象时,线程就会终止。

public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        t.interrupt();
}

2、守护线程和用户线程的设置

void setDaemon(boolean on)

使用setDaemon()方法可以将一个线程设置为守护线程或者用户线程(参数为TRUE时,是守护线程,参数为FALSE时为用户线程),一个线程被创建时默认是用户线程。

当用户线程和守护线程一起运行,当用户线程结束时,则守护线程就结束。

四、线程安全

线程异步:即多条线程同时执行一个任务时,这种情况下往往是出现数据错乱的情况,例如两个人同时对一个银行账户进行取钱,账户中有10000元,两个人同时取走5000元,结果账户中还剩余5000元。所以这种线程异步的情况是非常不安全的。

线程同步:即多条线程同时执行一个任务时,,当一个线程开始执行任务线程后,为这个任务加锁,其他线程等待次线程执行完任务线程后再进行抢夺执行任务的时间片。

1、实现线程安全的方法(以售票窗口买票为例)

1.1、synchronized方法(显式锁)

同步代码块

当一个售票窗口在卖票时,其他窗口等待。

语法:
synchronized (加锁对象){
        //Java语句    
}
class Runnable implements Runnable{
    private int count = 10;
    private Object o = new Object();
    public void run() {
        while(true){
            synchronized (o){
                if(count>0){
                    System.out.println(Thread.currentThread().getName()+"买票中");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("余票:"+count);
                }else{
                    break;
                }
            }
        }
    }
}
同步方法

当时用同步方法时,当前加锁的对象默认为当前对象this

class Runnable implements Runnable{
    private int count = 10;
    public void run() {
        while(true){
            boolean flag = sale();
            if(!flag){
                break;
            }
        }
    }

    public synchronized boolean sale(){
        //判断票数是否大于0,是返回true,否返回false
        if(count>0){
            System.out.println(Thread.currentThread().getName()+"买票中");
            try {
                //此处休眠0.5秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票卖出,票数减一
            count--;
            System.out.println("余票:"+count);
            return true;
        }else{
            return false;
        }
    }
}

1.2、Lock方法(隐式锁)

使用Lock方法需要创建Lock对象,并在需要加锁是手动加锁,在需要解锁时手动解锁

class Runnable implements Runnable{
    private int count = 10;
    private Lock l = new ReentrantLock();
    public void run() {
        while(true){
            l.lock();
            if(count>0){
                System.out.println(Thread.currentThread().getName()+"买票中");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("余票:"+count);
               l.unlock();
            }else{
                l.unlock();
                break;
            }
        }
    }
}

1.3、显式锁和隐式锁的区别

(1)Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。
(2)Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁。

(3)在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
(4)在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。

(5)Sync是不可中断的。除非抛出异常或者正常运行完成
(6)Lock可以中断的。

(7)Sync:非公平锁
(8)lock:两者都可以的。默认是非公平锁。ReentrantLock(boolean fair),true是公平锁,false是不公平锁

五、死锁

概述
A和B两人分别进入试衣间1和试衣间2,A想等B出来后去试衣间2,自己则在试衣间1中等待,B想等A出来后去试衣间1,自己则在试衣间2中等待,最终2个人都在等对方出来,但是对方都不出来,导致一直僵持。

public class DeadLockTest {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        //子线程中需要和主线程中同样的A和B对象
        R r = new R(a,b);
        Thread t = new Thread(r);
        t.start();
        b.say(a);
    }
}

class B{
    public synchronized void say(A a){
        System.out.println("BBBBB");
        a.reply();
    }
    public synchronized void reply(){
        System.out.println("bbbbb");
    }
}

class A{
    public synchronized void say(B b){
        System.out.println("AAAAA");
        b.reply();
    }
    public synchronized void reply(){
        System.out.println("aaaaa");
    }
}

class R implements Runnable{
    private A a;
    private B b;

    public R(A a, B b) {
        this.a = a;
        this.b = b;
    }
    public void run() {
        a.say(b);
    }
}

六、生产者与消费者

当厨师在做菜时,服务员休息状态,当厨师做完菜后,厨师休息,服务员端菜出去,等服务员端空盘子回来后,服务员继续休息,叫厨师继续做菜。依次循环

public class Test {
    public static void main(String[] args) {
        Food f = new Food();
        Cook c = new Cook(f);
        Waiter w = new Waiter(f);
        //厨师线程
        new Thread(c).start();
        //服务员线程
        new Thread(w).start();
    }
}

class Cook implements Runnable{
    private Food f;
    public Cook(Food f) {
        this.f = f;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            f.cook(i);
            try {
                //此处休眠0.5秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Waiter implements Runnable{
    private Food f;
    private boolean flag = true;
    public Waiter(Food f) {
        this.f = f;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            f.get();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Food{
    private String name;
    private String tasty;
    private boolean flag = true;

    public Food() {
    }

    public Food(String name, String tasty) {
        this.name = name;
        this.tasty = tasty;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTasty() {
        return tasty;
    }

    public void setTasty(String tasty) {
        this.tasty = tasty;
    }

    //厨师做菜
    public synchronized void cook(int i){
        if(flag){
            if(i % 2 == 0){
                this.setName("番茄炒蛋");
                this.setTasty("咸");
            }else{
                this.setName("糖醋排骨");
                this.setTasty("酸甜");
            }
            System.out.println("厨师做菜:"+this.getName()+",味道:"+this.getTasty());
        }
        flag = false;
        //唤醒其他线程
        this.notifyAll();
        try {
            //厨师线程休眠
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    //服务员端菜
    public synchronized void get(){
        if(!flag){
            System.out.println("服务员出菜:"+this.getName()+",味道:"+this.getTasty());
        }
        flag = true;
        //唤醒其他线程
        this.notifyAll();
        try {
            //服务员线程休眠
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

七、线程池(了解)

概述
当程序中需要执行许多的内容很少的线程时,线程创建所花费的时间就会多于每次线程运行的所耗费的时间,就是导致程序的效率大大降低。使用线程池的方法就可以为程序节省下创建线程的时间。

线程池的种类

ExecutorService service = Executors.创建方法
void execute(Runnable command)  //指挥线程池执行任务

1.缓存线程池
任务线程传入时自动分配线程,线程不够时自动创建新线程

Executors.newCachedThreadPool()    //创建缓存线程池

2.定长线程池
指定线程池线程的个数,任务线程传入时自动分配线程,线程不够时剩余任务线程排队等待线程池中的线程执行完毕

Executors.newFixedThreadPool(int nThreads) //创建定长线程池,传入线程池中线程的个数

3.单线程线程池
线程池中只有一个线程,任务线程传入时自动分配线程,一个任务执行时剩余任务线程排队等待线程池中的线程执行完毕

Executors.newSingleThreadExecutor()   //创建单线程线程池

4.周期定长线程池
指定线程池线程的个数,任务线程传入时自动分配线程,可以设定任务线程第一次执行的延时时间和之后每次执行的间隔时间

Executors.newScheduledThreadPool(int corePoolSize)   
//创建周期定长线程池,传入线程池中线程的个数
  service.schedule(Runnable command, long delay, TimeUnit unit)
  //线程定时执行,传入任务、时长和时长单位
  service.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
//周期性定时执行,传入任务,初次执行延时时长,周期间隔时长和时长单位

最后

欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!


前程有光
936 声望618 粉丝