Java-多线程基础总结

Java-多线程基础总结

一、线程的创建方式

1)继承Thread

2)实现Runnable接口

3)实现Callable接口

class MyThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println("thread-1");
    }
}

class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("thread-2");
    }
}
//实现Callable接口方式
class MyThread3 implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("thread-3");
        return 123;
    }
}
public class TestCreate {

    public static void main(String[] args) {
        
        new Thread(new MyThread1()).start();
        new MyThread2().start();
        
        // 实现Callable接口
        FutureTask<Integer> task = new FutureTask<>(new MyThread3());
        new Thread(task).start();

        // 获取线程的返回值
        try {
            Integer i = task.get();
            System.out.println(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

二、线程的状态

1)状态转换图

线程有6个状态:

状态########描述
NEW新创建当使用new操作创建一个线程时(new Thread(x)),此时处于新建态
Runnable就绪·(可运行)调用start()方法后,进入可运行状态
Blocked阻塞竞争不到对象锁而进入阻塞,获得锁进入可运行态
Waiting等待一个线程等待另一个线程通知调度器一个条件时,它使自己进入等待状态【生产者-消费者】,比如调用 wait方法join方法,或等待Lock或Condition,就会出现这种情况
Timed waiting计时等待这一状态将一直保持到超时期满或接到适当的通知
Terminated被终止run 方法正常结束,或run 方法出现异常而结束

在这里插入图片描述

1)状态流程

在这里插入图片描述

2)常见方法
方法名作用
静态方法Thread.sleep(int time)使当前线程进入休眠,不会放对象锁
静态方法Thread.yield()使当前线程让出CPU—>进入就绪态
实例方法t1.join()类似插队,其他线程需要等待t1结束后,才能继续
实例方法t1.get/setPriority(int n)用于获取当前和设置线程的优先级
实例方法t1.getState()获取线程状态
实例方法t1.setDaemon(true)设置当前为守护线程

三、线程同步

1)同步方法
  1. 使用synchronized关键字,它包括两种用法:synchronized 方法和synchronized块.

同步方法:

public synchronized void method(){}
  1. 同步方法锁的是对象本身this

  2. 缺陷:若将一个大的方法申明为synchronized将会影响效率

2)同步代码块
  1. 同步块:synchronized(obj){}

  2. obj 称之为同步监视器

    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
  3. 同步监视器的执行过程

    • 第一个线程访问,锁定同步监视器,执行其中代码.
    • 第二个线程访问,发现同步监视器被锁定,无法访问.
    • 第一个线程访问完毕,解锁同步监视器.
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

示例:银行取钱

/**
 * 2夫妻同时到银行取钱
 * 
 * 同步块:
 * @author a_apple
 * @create 2020-05-16 21:25
 */
//账户
class Account{
    int money; //余额
    String name; //卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Darwing extends Thread{

    Account account;
    //取了多少钱
    int drawMoney;
    //手里多少钱
    int nowMoney;

    public Darwing(String name, Account account, int drawMoney) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }

    // synchronized方法:默认锁的是this。这里如果锁run()方法的话,锁的对象就是Drawing对象,相当于锁住银行了
    @Override
    public void run() {
       takeMoney();
    }

    public void takeMoney(){
        // 锁的对象是变化的量,需要增删改的量
        synchronized (account){
            //余额不足
            if(account.money<drawMoney){
                System.out.println(this.getName()+"余额不足...");
                return;
            }

            try {
                //将2个线程都堵在这里,放大问题
                // you,girlFriend线程都发现有100,但是钱被girl取了,变为0
                // 当you醒过来以为还有100,就也取了50
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //取钱
            account.money -= drawMoney;
            nowMoney += drawMoney;

            System.out.println(this.getName()+"取了:"+drawMoney);
            System.out.println(account.name+"余额有:"+account.money);
            System.out.println(this.getName()+"手里有:"+nowMoney+"\n");
        }
    }
}

public class UnsafeBank {

    public static void main(String[] args) {
        Account account = new Account(100, "结婚基金");

        Darwing you = new Darwing("you",account,50);
        Darwing girl = new Darwing("girl",account,100);

        you.start();
        girl.start();
    }
}
3)死锁

形成条件:线程互相持有对方需要的资源—>循环等待

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

在这里插入图片描述

如何解决:破坏其中一个或多个条件即可

/**
 * 测试死锁
 *      多个线程互相拥有对方需要的资源,形成僵持
 * @author a_apple
 * @create 2020-05-16 22:54
 */
class Player extends Thread {

    private String lock1;
    private String lock2;

    Player(String lock1, String lock2, String name) {
        super(name);
        this.lock1 = lock1;
        this.lock2 = lock2;
    }

    @Override
    public void run() {
        try {
            //确保2线程都启动
            Thread.sleep(1000);
            noDeadLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //死锁:持有锁,并想获得对方的锁
    void deadLock() throws InterruptedException {
        synchronized (lock1) {
            System.out.println(this.getName() + " have " + lock1);
            System.out.println(this.getName() + " need " + lock2);

            //保证2线程都启动
            Thread.sleep(1000);

            synchronized (lock2) {
                System.out.println(this.getName() + " get " + lock2);
            }
        }
    }

    //解决死锁:持有锁,等待对方放弃锁
    void noDeadLock() throws InterruptedException {
        synchronized (lock1) {
            System.out.println(this.getName() + " have " + lock1);
            System.out.println(this.getName() + " need " + lock2);

            Thread.sleep(1000);
        }
        //放弃lock1,请求lock2
        synchronized (lock2) {
            System.out.println(this.getName() + " get " + lock2);
        }
    }
}

public class TestDeadLock {

    private static String lock1 = new String("lock-V");
    private static String lock2 = new String("lock-H");

    public static void main(String[] args) {
        Player a = new Player(lock1, lock2, "A");
        Player b = new Player(lock2, lock1, "B");

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

示例:买票

/**
 * 使用Lock锁
 * @author a_apple
 * @create 2020-05-21 9:53
 */
class Tickets2{
    // 票的数量
    private int num = 20;

    private Lock lock = new ReentrantLock();
    // 买票的方法
    public void sale(){
        lock.lock();
        try{
            if(num<=0){
                return;
            }
            System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票");
        }finally {
            lock.unlock();
        }
    }
}
public class SaleTicket2 {

    public static void main(String[] args) throws InterruptedException {

        Tickets2 res = new Tickets2();

        // 确保线程启动
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            for (int i = 0; i < 8; i++) {
                res.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 8; i++) {
                res.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 8; i++) {
                res.sale();
            }
        },"C").start();
    }
}

输出:

A卖出了第20张票
A卖出了第19张票
A卖出了第18张票
A卖出了第17张票
A卖出了第16张票
A卖出了第15张票
A卖出了第14张票
A卖出了第13张票
B卖出了第12张票
B卖出了第11张票
B卖出了第10张票
B卖出了第9张票
B卖出了第8张票
B卖出了第7张票
B卖出了第6张票
B卖出了第5张票
C卖出了第4张票
C卖出了第3张票
C卖出了第2张票
C卖出了第1张票

四、线程通信

多个线程互相协作,共同完成任务。

涉及的方法

wait()使当前线程进入阻塞,并释放锁
wait(int time)等待指定的毫秒数
notify()唤醒一个等待该(对象锁)线程并使该线程开始执行
notifyAll()notifyAll 会唤醒所有等待该(对象锁)线程,

注意:

  • wait()、notify()、notifyAll()是继承自Object的本地final方法
  • 上面的方法需要配合synchronized关键字使用,即放在同步方法或代码块中使用。【说明当前线程已经获得锁
1)生产者-消费者问题
  • 生产者—>缓冲区 缓冲区满–>等待消费
  • 消费者<—缓冲区 缓冲区空–>等待生产
package pers.xu.multithread.kuangshen.pc;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 生产者--消费者
 *
 *      生产者--->缓冲区   缓冲区满-->通知消费者消费
 *      消费者<---缓冲区   缓冲区空-->通知生产者生产
 *
 * 实例:生产者,消费者,产品,缓冲区
 * @author a_apple
 * @create 2020-05-17 10:03
 */

class Product{
    //产品编号
    int i;
    public Product(int i) {
        this.i = i;
    }
}

class ProductBuffer{
    // 缓冲区大小 5
    List<Product> buffer = new ArrayList<>();
    private int maxCapacity = 5;

    // 将产品放入缓冲区
    public void push(Product product){
        // 实际操作的是buffer,所以这里使用buffer锁
        synchronized (buffer){
            // 缓冲区满-->放弃锁-->阻塞
            while (maxCapacity == buffer.size()){
                try {
                    System.out.println("缓冲区已满...");
                    buffer.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //放入
            buffer.add(product);
            System.out.println(Thread.currentThread().getName()+"生产了:"+product.i);

            //通知消费
            buffer.notifyAll();
        }
    }

    // 从缓冲区获取商品   synchronized锁的this==缓冲区
    public Product pop(){
        synchronized (buffer){
            // 缓冲区空-->等待
            while(buffer.size()==0){
                try {
                    //进入阻塞-->放弃buffer锁
                    System.out.println("---->缓冲区空了<----");
                    buffer.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //随机取出一个
            Product product = buffer.remove(new Random().nextInt(buffer.size()));
            System.out.println(Thread.currentThread().getName()+"-->消费了:"+product.i);

            //通知生产
            buffer.notifyAll();
            //返回产品
            return product;
        }
    }
}

public class TestPC {

    public static void main(String[] args) {

        ProductBuffer buffer = new ProductBuffer();

        // 生产者-1   
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                //放入10个产品
                buffer.push(new Product(i));
                //sleep(1000): 每生产一个就停一下
            }
        },"A").start();

        // 生产者-2
        new Thread(()->{
            for (int i = 11; i <= 20; i++) {
                //放入10个产品
                buffer.push(new Product(i));
            }
        },"C").start();

        // 消费者-1
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                buffer.pop();
            }
        },"B").start();

        // 消费者-2
        new Thread(()->{
            for (int i = 11; i <= 20; i++) {
                buffer.pop();
            }
        },"D").start();
    }
}


结果:

A生产了:1
A生产了:2
A生产了:3
A生产了:4
A生产了:5
缓冲区已满...
B-->消费了:1
B-->消费了:2
B-->消费了:5
B-->消费了:3
B-->消费了:4
---->缓冲区空了<----
C生产了:11
C生产了:12
C生产了:13
C生产了:14
C生产了:15
缓冲区已满...
B-->消费了:12
B-->消费了:15
B-->消费了:13
B-->消费了:14
B-->消费了:11
A生产了:6
A生产了:7
A生产了:8
A生产了:9
A生产了:10
D-->消费了:9
D-->消费了:10
D-->消费了:6
D-->消费了:7
D-->消费了:8
---->缓冲区空了<----
C生产了:16
C生产了:17
C生产了:18
C生产了:19
C生产了:20
D-->消费了:20
D-->消费了:19
D-->消费了:16
D-->消费了:18
D-->消费了:17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值