你还敢说不懂Java多线程

进程与线程

概念

进程:进程是程序的一次执行过程,是程序执行过程中分配和管理资源的基本单位,每个进程都有自己的内存空间。

线程:线程是CPU资源调度和分派的基本单位,它可以和同一进程下的其他线程共享进程资源。

联系

线程是进程的一部分,一个进程可以有多个线程,但一个线程只能存在于一个进程中。

区别

根本区别:进程是操作系统资源调度的基本单位,线程是执行调度的基本单位。
开销方面:进程的切换开销大,线程的切换开销小。
共享方面:进程拥有自己独立的地址空间和资源,线程是共享所属进程的资源。

Java多线程的实现方式

继承Thread类

package com.Thread.create;

//继承Thread类,重写run()方法
class MyThread extends Thread{

    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

public class ThreadDemo_one {

    public static void main(String[] args) {

        //创建线程,并执行
        Thread my_thread = new MyThread();
        my_thread.start();

        //主线程的执行
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }

}

实现Runnable接口

package com.Thread.create;

//实现Runnable接口,重写run()方法
class My_Runnable implements Runnable{

    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

public class ThreadDemo_two {

    public static void main(String[] args) {

        //创建线程,并执行
        //1、创建Runnable对象
        My_Runnable me = new My_Runnable();
        //2、把Runnable对象传递到Thread类中
        Thread t = new Thread(me);
        //3、开启线程
        t.start();

        //主线程的执行
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }
}

实现Callable接口

package com.Thread.create;

//实现Callable接口,重写call()方法

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class My_Callable implements Callable{

    @Override
    public Object call() throws Exception {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return null;
    }
}

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

        //创建线程,并执行
        //创建Callable对象
        Callable my_callable = new My_Callable();
        //使用FutureTask类包装Callable对象
        FutureTask my_task = new FutureTask(my_callable);
        //将FutureTask传递给Thread类
        Thread t = new Thread(my_task);
        t.start();

        //主线程的执行
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }
}

使用线程池

package com.Thread.create;

//使用线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class My_runnable implements Runnable{

    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

public class ThreadDemp_four {

    public static void main(String[] args) {

        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //创建Runnable对象
        Runnable me = new My_runnable();
        //从线程池中获取线程对象,把Runnable对象传递过去
        executorService.submit(me);
        //关闭线程池。生产环境是不关的
        executorService.shutdown();

        //主线程的执行
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+ ":" + i);
        }
    }

}

四种创建方式的优缺点比较

实现Runnable接口比继承Thread类所具有的优势
1、避免了Java单继承的局限性
2、适合多个相同的程序代码去共享同一个资源
3、线程池中只能放入实现Runnable或Callable类线程,不能放入继承Thread类

Callable与Runnable相比
最主要区别是 call() 方法有返回值,而 run() 方法没有

使用线程池的好处
1、提高响应速度,减少创建线程的时间
2、降低了资源消耗,重复利用线程池中的线程
3、便于线程的管理,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下

线程的同步(线程的安全)

线程安全

所谓的线程安全就是有多个线程同时运行,并且这些线程可能同时运行这一段代码。程序每次运行结果和单线程运行的结果是一样,并且变量的值和预期的一样。

多线程访问了共享资源才会出现线程安全问题

线程同步

使用同步机制(synchronized)可以解决多线程访问同一资源出现的线程安全问题

下面买票的类就是线程不安全的类

package com.Thread.synchronize;

public class Sell_Ticket implements Runnable{

    int ticket = 100;
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

同步代码块

synchronized(同步锁){
        需要同步操作的代码
}
同步锁:
1、可以是任意对象
2、多个线程对象,需要使用同一把锁
package com.Thread.synchronize;

//使用同步代码块实现线程安全问题
/*
synchronized(同步锁){
        需要同步操作的代码
}
同步锁:
1、可以是任意对象
2、多个线程对象,需要使用同一把锁
*/

public class Sell_Ticket_Synchronized_one implements Runnable{

    private int ticket = 100;
    //创建一个锁对象
    Object obj = new Object();
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

同步方法

public synchronized void method(){
	可能会产生线程安全问题的代码
}

同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
package com.Thread.synchronize;

//使用同步方法实现线程安全问题
/*
同步方法的锁对象就是它自己this
 */


public class Sell_Ticket_Synchronized_two implements Runnable{

    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            sell_ticket();
        }
    }

    private synchronized void sell_ticket(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }

    }
}

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
package com.Thread.synchronize;

//使用锁对象实现线程安全问题

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

public class Sell_Ticket_Synchronized_three implements Runnable{

    private int ticket = 100;
    //创建锁对象
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //加锁
            lock.lock();
            try {
                Thread.sleep(10);
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }

}

线程的通信(生产者与消费者)

线程通信

概念

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

简单总结就是:我们希望线程有规律地执行

如何通信

使用等待唤醒机制

等待唤醒机制

什么是等待唤醒

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。

就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。

等待唤醒的方法

1、wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2、notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3、notifyAll:则释放所通知对象的 wait set 上的全部线程。

调用wait和notify方法需要注意的细节
1、wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2、wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

生产者消费者模式

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

代码实现

包子类

package com.Thread.communication;

//包子类,也叫资源类
public class BaoZi {
    //包子皮
    String pi;
    //包子馅
    String xian;
    //包子的状态,表示资源是否存在
    boolean flag = false;
}

生产包子的类

package com.Thread.communication;

//包子铺,表示生产者,生产包子
public class BaoZi_Producer extends Thread{

    private BaoZi baozi;

    public BaoZi_Producer(BaoZi baozi){
        this.baozi = baozi;
    }

    @Override
    public void run() {
        int count = 0;
        //一直生产,用while(true)循环
        while (true){
            synchronized (baozi){
                if (baozi.flag == true){
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //增加趣味性
                if (count%2==0){
                    baozi.pi = "薄皮";
                    baozi.xian = "牛肉馅";
                    System.out.println("包子铺生产包子-->"+baozi.pi+baozi.xian+"包子");
                }else{
                    baozi.pi = "脆皮";
                    baozi.xian = "猪肉馅";
                    System.out.println("包子铺生产包子-->"+baozi.pi+baozi.xian+"包子");
                }
                //做个包子要5秒钟
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                baozi.flag = true;
                baozi.notify();
            }
        }
    }
}

吃包子的类

package com.Thread.communication;

//包子消费者
public class BaoZi_Comsumer extends Thread{

    private BaoZi baozi;

    public BaoZi_Comsumer(BaoZi baozi){
        this.baozi = baozi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baozi){

                if (baozi.flag==false){
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃包子-->"+baozi.pi+baozi.xian+"包子");
                //吃包子3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                baozi.flag = false;
                baozi.notify();
                System.out.println("----------------------------------");
            }
        }
    }
}

测试类

package com.Thread.communication;

public class Test {

    public static void main(String[] args) {
        BaoZi baozi = new BaoZi();
        BaoZi_Producer bp = new BaoZi_Producer(baozi);
        BaoZi_Comsumer bc = new BaoZi_Comsumer(baozi);
        bp.start();
        bc.start();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值