JavaSE第十章.线程

程序进程线程

  • 程序

    程序(program),是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,存在于硬盘当中

  • 进程

    进程((process),就是正在执行的程序,进程是程序运行和操作系统资源分配的最小单位

  • 线程

    线程(thread),进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元

线程和进程之间的区别,我觉得可以用这个例子来看出两者的不同,进程就是一栋房子,线程就是住在房子里的人。进程是一个独立的个体,有自己的资源,线程是在进程里的,多个线程共享着进程的资源。

  1. 一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
  2. 每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main()方法就是在主线程中被执行的。
  3. 在主线程中可以创建并启动其它的线程;
  4. 一个进程内的所有线程共享该进程的内存资源。

线程互斥的4中方式

在这里插入图片描述

创建线程

线程代码存放Thread子类run方法中

启动线程一定不能调用run() , 否则就是普通方法调用

1.继承Thread类

public class MyThread extends Thread {      //继承Thread类
    @Override              ////重写run方法
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("Thread:"+i);
        }
    }
}
public class Text {    //主线程
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();    //启动线程

        for (int i = 0; i < 10000; i++) {
            System.out.println("main:"+i);
        }
    }
}

2.实现Runnable接口

线程代码存在接口的子类的run方法

Runnable接口的存在主要是为了解决Java中不允许多继承的问题

public class MyThread implements Runnable{
    @Override            //创建一个线程作为外壳,将myThread包起来
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("thread"+i);
        }
    }
} 

public class Text {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread =new Thread(myThread);  
        thread.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println();
        }
    }
}

实现Runnable的好处

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源

常用方法

sleep()
Thread类下的静态本地方法,调用此方法会让当前线程进入休眠状态,不会释放当前占有的锁,需要处理异常

wait()
Object类下的方法,调用此方法会让线程进入waiting状态,只有等待其他线程的通知唤醒或被中断后才会返回,会释放占有的锁,一般用在同步代码块中完成线程通信

notify()
Object类下的方法,对于被wait()进入等待的线程,会对这些线程随机唤醒一个

yield()
Thread类下的静态本地方法,会让当前线程让出CPU执行时间片,与其他线程重新竞争CPU时间片。

interrupt()
向线程发出一个中止信号。不能中断在运行中的线程,它只能改变中断状态而已,实际完成的是让受阻塞的线程退出阻塞状态;也就是如果当前线程正在睡眠:则抛出InterruptedException异常并提前退出阻塞状态

join()
用于等待其他线程终止,A线程调用B线程的join(),A会等待B线程执行完成自己才会执行,在此期间A进入阻塞状态

sleep()和wait()

  • sleep是Thread类中的静态方法,Wait是Object类的本地方法
  • sleep不会释放锁,而wait会释放锁,加入等待队列中
  • sleep自动唤醒,wait需要通过notify唤醒
  • sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
  • sleep可以在任何地方使用, 而wait只能在同步控制方法或者同步控制块里面使用(synchronized)
  • sleep是当前线程用于放缓休眠,而wait用于多线程之间通信(其他线程可以调用)

yeild()和join()

  • yeild直接进入就绪状态,join进入阻塞状态,等待这个线程死亡(执行完)

为什么不调用run()

new一个Thread会使线程进入新建状态,使用start()会准备线程启动的相关的操作,获取到时间片后会自动执行run()中的方法;如果执行run(),会把其当作一个普通的函数调用。

线程优先级

  • 计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;

  • 优先级较高的线程有更多获得CPU的机会,反之亦然;

  • 优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority()和getPriority()方法来设置或返回优先级;

线程状态

在这里插入图片描述

  • 新建:当一个线程类new出一个实例后,就进入新建状态
  • 就绪:具备了运行的条件,只是没分配到CPU资源
  • 运行:获得CPU执行权
  • 阻塞:线程被挂起,让出 CPU执行权,进入阻塞状态
    1. 等待阻塞: 调用wait()后,JVM会将线程放入等待序列 waiting queue
    2. 同步阻塞:获取同步锁失败,进入锁池 lock pool
    3. 其他阻塞:sleep(),join(),等待同步锁
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常 导致结束

线程阻塞三种形式

线程的分类

用户线程

用来编写方法线程

守护线程

  • 任何一个守护线程都是整个JVM中所有非守护线程的保姆

  • 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作

  • 守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

  • 用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没 有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了

  • 设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常

        SHThread shThread = new SHThread();

        shThread.setDaemon(true);   //设置为守护线程
        shThread.start();

多线程

概念

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

何时需要多线程

  • 程序需要同时执行两个或多个任务

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等

  • 需要一些后台运行的程序时

优点

  • 提高程序的响应
  • 提高CPU的利用率
  • 改善程序结构,将复杂任务分为多个线程,独立运行

缺点

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
  • 多线程需要协调和管理,所以需要CPU时间跟踪线程
  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
public class ManyThread implements Runnable  

public static void main(String[] args) {
        ManyThread manyThread = new ManyThread();

        Thread mt1 = new Thread(manyThread);  //创建线程对象
        mt1.setName("窗口1");
        Thread mt2 = new Thread(manyThread);
        mt2.setName("窗口2");

        mt1.start();
        mt2.start();
    }

线程同步

并发与并行

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事(同一个对象被多个线程同时操作)

多线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到

(买票,取款)

synchronized

在这里插入图片描述

修饰代码块
  • synchronized (同步监视器(obj)){ // 需要被同步的代码; }

synchronized在继承Thread情况下修饰代码块时 , 因为创建了两个Thread对象 , 为了保证只有一个进入synchronized修饰的代码块内 , 所以static Object obj = new Object(); obj表示锁对象

但如果在继承Runnable接口的情况下 , 只new了一个对象 , 所以直接用synchronized (this) {…}修饰(表示锁对象)

//修饰代码块
public class ManyThread extends Thread {
    static int num = 20;
    static Object obj = new Object();//保证只有一个

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { //创建同步锁(对象)
                if (num > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-----" + num);
                    num--;
                } else {
                    break;
                }
            }
        }
    }
}
修饰方法

synchronized还可以放在方法声明中,表示整个方法为同步方法。

public synchronized void show (String name){

// 需要被同步的代码; }

synchronized在修饰方法时 , 同步对象(锁)默认是this , 一旦创建多个线程对象 (只针对继承Thread类的方式)–用static修饰 : static修饰后 , 同步对象变为 线程类(类只有一个)

public class TicketThread extends Thread{
    static int num = 10;//票数  共享资源

    @Override
    public void run() {
        while(true){
            if(num>0){
                print();
            }else{
                break;
            }
        }
    }

    /*
      synchronized在修饰方法时,同步对象(锁), 默认是this  一旦创建多个线程对象(只针对继承Thread类的方式)
      static修饰后,同步对象变为 线程类(类只有一个)
     */
    public static synchronized void print(){  //静态
        if(num>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售出票:"+num);//0
            //线程1再减之前   0
            num--;
        }
    }
}

implements Runnable情况下不用加static

//购票
public class BuyTicket implements Runnable {
    static int num = 100;

    @Override
    public synchronized void run() {
        while (num > 0) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
            System.out.println(Thread.currentThread().getName() + "\t已购买,剩余" + num + "张票");
        }
    }

    public void buy() {
        num--;
    }
}


public class Text {
    public static void main(String[] args) {
        BuyTicket mythread = new BuyTicket();

        //两个线程执行一个任务  num是只有一个的
        Thread thread1 = new Thread(mythread, "t1");
        Thread thread2 = new Thread(mythread, "t2");

        thread1.start();
        thread2.start();
    }
}
  • 同步就是排队+锁

    几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作

    为了保证数据在方法中被访问时的正确性,在访问时加入锁机制

  • 同步监视器

    同步监视器可以是任何对象,必须唯一,保证多个线程获得是同一个对象 (锁).

  • 同步监视器的执行过程

    1.第一个线程访问,锁定同步监视器,执行其中代码.

    2.第二个线程访问,发现同步监视器被锁定,无法访问.

    3.第一个线程访问完毕,解锁同步监视器.

    4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问.

LOCK

通过显式定义同步锁对象来实现同步

synchronized–>隐式加锁 , 自动释放锁 (可以锁定代码块, 也可以锁定方法)

Lock --> 可以显示的加锁 , 需要释放 , 只能锁定代码块

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁

private Lock lock = new ReentrantLock(); //添加锁对象

lock.lock(); //获得锁

lock.unlock(); //释放锁

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();    //添加锁对象

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();         //获得锁
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (101 - tickets) + "张票");
                    tickets--;
                }
            }finally {
                lock.unlock();         //释放锁
            }
        }
    }
}

线程死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步 资源,就形成了线程的死锁。出现死锁后,不会报异常,只是所有的线程都处于阻塞状态,无法继续运行

产生死锁的必要条件

  1. 互斥条件:一个资源任意时刻只能被一个线程占用
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不可剥夺条件:进程已获得的资源不能被剥夺,只能自己用完释放
  4. 循环等待条件:多线程之间形成一种头尾相连循环等待资源的关系

避免死锁的方式

  1. 破坏请求和保持条件:一次性申请所有资源(小锁变大锁)
  2. 破坏不可剥夺条件:一个线程请求不到他的资源,可以主动释放自己的资源
  3. 锁排序法:如果某个线程需要获得A锁和B锁才能对资源进行操作,那我们可以设定让其获取到A锁才有资格获取到B锁

死锁示例

public class DieLock extends Thread{
    static Object objA = new Object();
    static Object objB = new Object();  //两个锁

    boolean flag;

    DieLock(boolean flag){
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag){     //线程1
            synchronized (objA){
                System.out.println("ifobjA");
                synchronized (objB){
                    System.out.println("ifobjB");
                }
            }
        }else {     //线程2
            synchronized (objB){
                System.out.println("elseobjB");
                synchronized (objA){
                    System.out.println("elseobjA");
                }
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);

        dieLock1.start();
        dieLock2.start();
    }
}

--------------------
ifobjA
elseobjB

哲学家就餐

都先拿左筷子再拿右筷子,会出现死锁

让一个人先拿右边,解决,但是要一个一个吃,效率低

所以每隔一个人让这个人先拿右边

线程通信

线程通讯指的是多个线程通过消息传递实现相互牵制,相互调度,即线程间的相互作用

notify()

{ }

wait() 释放锁

  • .wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

  • .notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

  • .notifyAll一旦执行此方法,就会唤醒所有被wait的线程。

说明:

.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中

案例一 : 两个线程交替打印1~100

public class PrintNum extends Thread{

    static int num = 0;
    static Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized (obj){
                obj.notify();   //唤醒等待的线程,由于已经有线程持有锁了,也是不能进入到同步代码块
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(num<100){
                    num++;
                    System.out.println(Thread.currentThread().getName()+":"+num);
                }else{
                    break;
                }
                try {
                    obj.wait();  //线程等待,释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {

        PrintNum printNum1 = new PrintNum();
        PrintNum printNum2 =new PrintNum();

        printNum1.start();
        printNum2.start();

    }
}

案例二 :生产者/消费者问题

生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待

//柜台
public class Counter {
    int num = 0;

    // 同步对象时this  就是同一个Counter对象
    public synchronized void add(){
        if(num==0){
            num++;
            System.out.println("生产一个,剩余"+num+"个");
            this.notify();   //商品数量足够,唤醒消费者
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void sub(){
        if(num>0){
            num--;
            System.out.println("售出一个,剩余"+num+"个");
            this.notify();
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者消费线程
public class Customer extends Thread{

    Counter counter;

    public Customer(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter.sub();
        }
    }
}

//生产者生产线程
public class Productor extends Thread{

    Counter counter;

    public Productor(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter.add();
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Counter counter = new Counter();   //创建两个线程的同一共享对象

        Customer customer = new Customer(counter);
        Productor productor =new Productor(counter);

        customer.start();
        productor.start();
    }
}

--------------------------------------
生产一个,剩余1个
售出一个,剩余0个
生产一个,剩余1个
售出一个,剩余0......

新增创建线程方式

实现Callable接口

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,获取返回结果
//线程
public class CallableThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int a = 100;
        return a;           //返回a
    }
}



//测试
public class Text {
    public static void main(String[] args) {
        CallableThread callableThread = new CallableThread();

        FutureTask<Integer> futureTask= new FutureTask<Integer>(callableThread);   //借助FutureTask类,获取返回结果
        Thread t = new Thread(futureTask);     //实类化

        t.start();

        try {
            Integer res = futureTask.get();     //获得线程call方法的返回值
            System.out.println(res);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EnndmeRedis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值