多线程的三种实现方法、线程类的常见方法、线程安全问题:synchronized和Lock锁、生产者和消费者问题

1. 多线程的三种实现方式

1.1 继承Thread

实现步骤:

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程start
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //start() 开启线程; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

 1.2 实现Runnable接口

实现步骤:

  • 定义一个类MyRunnable实现Runnable接口
  • 在MyRunnable类中重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飞机");

        //启动线程
        t1.start();
        t2.start();
    }
}

1.3 利用CallableFuture接口实现

实现步骤:

  • 定义一个类MyCallable实现Callable接口
  • 在MyCallable类中重写call()方法
  • 创建MyCallable类的对象
  • 创建Future的实现类FutureTask对象,把MyCallable对象作为其构造方法的参数
  • 创建Thread类的对象,把FutureTask对象作为其构造方法的参数
  • 启动线程
  • 再调用get方法,就可以获取线程结束之后的结果
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟她表白第" + i + "次");
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);

        //创建线程对象
        Thread t1 = new Thread(ft);

        //开启线程
        t1.start();
		//得到线程运行完毕后的结果
        String s = ft.get();
        System.out.println(s);
    }
}

2. 线程类的常见方法

设置和获取线程名称:

方法名说明
void setName(String name)将此线程的名称更改为参数name
String getName( )返回此线程的名称
Thread currentThread( )返回对当前正在执行的线程对象的引用
class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

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

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //void setName(String name):将此线程的名称更改为等于参数 name
        my1.setName("高铁");
        my2.setName("飞机");

        my1.start();
        my2.start();
        //static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());//main
    }
}

线程类的其他方法:

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

3. 线程的安全问题

当多个线程一起操作共享数据时会产生数据安全问题

解决方法:

  • 同步代码块:synchronized(任意对象),相当于给代码加锁了,任意对象就可以看成是一把锁
  • 同步方法:把synchronized关键字加到方法的返回值类型前,同步方法的锁对象是this、同步静态方法的锁对象:类名.class
  • Lock锁:Lock中提供了获得锁和释放锁的方法

同步代码块:

public class Ticket implements Runnable {
    private int ticket = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //对可能有安全问题的代码加锁,多个线程必须使用同一把锁
            synchronized (obj) {
                if (ticket == 0) {
                    break;
                } else {
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(
                            Thread.currentThread().getName() +
                                    "卖了一张票, 还剩" + ticket + "张");
                }
            }
        }
    }
}

同步方法:

public class Ticket implements Runnable{
    private int ticket = 100;
    
    @Override
    public void run() {
        while(true){
            boolean result = synchrMethed();
            if (result){
                break;
            }
        }
    }

    private synchronized boolean synchrMethed() {
        if (ticket == 0){
            return true;
        }else {
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName()+" 卖了一张票,还剩 " + ticket +"张票");
            return false;
        }
    }
}

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化:

import java.util.concurrent.locks.ReentrantLock;

public class Ticket02 implements Runnable {
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket == 0) {
                    break;
                } else {
                    Thread.currentThread().sleep(100);
                    ticket--;
                    System.out.println(
                            Thread.currentThread().getName() + "卖了一张票, 还剩" + ticket + "张");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

4. 死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程都处于等待状态,无法执行的状态

在资源有限、同步嵌套的情况下会产生死锁现象

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println("小康同学正在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA){
                        System.out.println("小薇同学正在走路");
                    }
                }
            }
        }).start();
    }
}

5. 经典同步问题:生产者—消费者问题

问题描述:

生产者和消费者进程,共享一个初始为空、容量为1的缓冲区,只有缓冲区为空时,生产者才能将消息放入缓冲区,否则等待;只有缓冲区不空时,消费者才能从缓冲区取出消息,否则等待。缓冲区为临界资源,同一时间只允许消费者或者生产者访问。

Object类的等待和唤醒方法:

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

缓冲区:

public class Buffer {
    //定义一个标记
    //true 就表示缓冲区有消息,允许消费者执行
    //false 就表示缓冲区没有消息,允许生产者执行
    private boolean flag;

    //count表示生产者最多生产消息的数量
    private int count;

    //锁对象
    private final Object lock = new Object();

    public Buffer() {
        this(false,10); //默认缓冲区没有消息,生产者生产10个消息就退出程序
    }

    public Buffer(boolean flag, int count) {
        this.flag = flag;
        this.count = count;
    }

    public boolean isFlag() {
        return flag;
    }

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

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Object getLock() {
        return lock;
    }

    @Override
    public String toString() {
        return "Desk{" +
                "flag=" + flag +
                ", count=" + count +
                ", lock=" + lock +
                '}';
    }
}

生产者:

public class Producer extends Thread{
    private Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while(true){
            synchronized (buffer.getLock()){
                if(buffer.getCount() == 0){
                    break;
                }else{
                    if(!buffer.isFlag()){
                        //生产
                        System.out.println("生产者生产一个消息放入缓冲区");
                        buffer.setFlag(true);
                        buffer.getLock().notifyAll();
                    }else{
                        try {
                            buffer.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

消费者:

public class Consumer extends Thread{
    private Buffer buffer;

    public Consumer(Buffer desk) {
        this.buffer = desk;
    }

    @Override
    public void run() {

        //套路:
        //1. while(true)死循环
        //2. synchronized 锁,锁对象要唯一
        //3. 判断,共享数据是否结束. 结束
        //4. 判断,共享数据是否结束. 没有结束
        while(true){
            synchronized (buffer.getLock()){
                if(buffer.getCount() == 0){
                    break;
                }else{
                    if(buffer.isFlag()){
                        //有
                        System.out.println("消费者从缓冲区取出一个消息");
                        buffer.setFlag(false);
                        buffer.getLock().notifyAll();
                        buffer.setCount(buffer.getCount() - 1);
                    }else{
                        //没有就等待
                        //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
                        try {
                            buffer.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    }
}

测试:

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

        Buffer buffer = new Buffer();

        Consumer consumer = new Consumer(buffer);
        Producer producer = new Producer(buffer);

        consumer.start();
        producer.start();
    }
}

使用阻塞队列实现等待唤醒机制

常见阻塞队列BlockingQueue:

  • ArrayBlockingQueue: 底层是数组,有界
  • LinkedBlockingQueue: 底层是链表,无界。但不是真正的无界,最大为int的最大值

BlockingQueue的核心方法:

  • put(anObject): 将参数放入队列,如果放不进去会阻塞
  • take(): 取出第一个数据,取不到会阻塞
import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args) {
        //定义一个阻塞队列,容量为1
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
        Consumer1 consumer1 = new Consumer1(bd);
        Producer1 producer1 = new Producer1(bd);
        consumer1.start();
        producer1.start();
    }
}
//消费者
class Consumer1 extends Thread{
    private ArrayBlockingQueue<String> bd;

    public Consumer1(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }

    @Override
    public void run() {
        while(true){
            try {
                bd.take();
                //System.out.println("消费者取出一个消息");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//生产者
class Producer1 extends Thread{
    private ArrayBlockingQueue<String> bd;

    public Producer1(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }

    @Override
    public void run() {
        while (true){
            try {
                bd.put("消息");
                //System.out.println("生产者生产一个消息");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

如有错误欢迎留言评论,及时更正。 2021年6月15日  羽露风

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

羽露风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值