基础——线程

一、线程执行的内存原理

	public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

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

对应的内存原理图大致是这样:

在这里插入图片描述

注意事项:

  1. 执行线程任务的run方法是线程私有的。
  2. 某个线程对象出现异常不会影响其他线程的执行。

二、创建线程的方式

(1)继承Thread类

1、步骤

  1. 定义一个类,继承Thread类。
  2. 重写Thread类的run方法。
  3. 创建线程对象。
  4. 调用start方法开启新线程,内部会执行run方法。

2、代码示例

public class MyThread extends Thread {
    @Override
    public void run() {
        // 获取线程名称
        String threadName = this.getName();
        for (int i=0;i<100;i++) {
        	// 复习异常抛出的方法,抛出一个运行时异常
            if("Thread-1".equals(threadName) && i == 3){
                throw new RuntimeException(threadName + "出问题了");
            }
            System.out.println(threadName+"..."+i);
        }
    }
}

3、Thread类的构造函数

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

4、常用成员方法

  • public String getName():获取当前线程名称。
  • public String start();导致此线程开始执行;java虚拟机调用此线程的run方法。
  • public void run():定义线程的任务逻辑。

5、常用静态方法

  • public static void sleep(long millis): 让当前正在执行的进程暂停指定毫秒数。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

(2)实现Runnable接口

1、步骤

  1. 定义Runnable接口的实现类
  2. 实现类覆盖重写run方法,指定线程任务逻辑
  3. 创建Runnable接口实现类对象
  4. 创建Thread类对象,构造函数传递Runnable实践类对象。

a) public Thread(Runnable target):分配一个带有指定目标新的线程对象。
b) public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

  1. Thread类对象调用start方法,内部自动调用run方法。

2、代码展示

public class MyTask implements Runnable{
    @Override
    public void run() {
        //任务逻辑
    }
}
	MyTask myTask = new MyTask();
    Thread thread = new Thread(myTask);
    thread.start();

2、(重点!)实现Runnable接口来创建线程的好处

  1. 避免java中类单继承的局限性
  2. 降低线程任务对象和线程之间的耦合性

tip:换句话说,我们可以更加专注于线程的任务,先把线程的任务逻辑创建完毕。之后需要执行线程任务的地方就创建线程,执行需要的线程任务即可。并且线程任务可以多次反复使用。有点像零件插拔一样。

(3)匿名内部类方式

1、格式

new 父类/接口(){
	//覆盖重写抽象方法
};

2、作用

  1. 创建父类子类对象的快捷方式
  2. 创建接口的实现类对象的快捷方式

3、注意事项

  1. 使用匿名内部类创建的对象只能一次性使用
  2. 尽量使用lambda表达式进行书写,提高代码可读性和编程效率。

三、(重点!)线程安全问题

(1)出现线程安全的情况

  1. 两个以上线程同时操作共享数据

  2. 操作共享数据的语句两条以上

  3. 线程调度是抢占式调度模式。

(2)解决方案

  • 同一个线程,操作共享数据的多条语句全部执行,要么多条语句全部不执行。故而可以使用同步技术。

  • 同步的原理:有锁的线程执行,没有锁的线程等待。

(3)实际解决

1、同步代码块

  1. 作用:用来解决多线程访问共享数据安全问题

  2. 格式

synchronized(任意对象){

}
  1. 注意事项
    (1)所有操作共享数据的代码写到同步代码块{}中。
    (2)任意对象:任意指的是类型可以任意,但要保证全局唯一,被多个线程共享使用
    1. 任意对象,也叫锁对象。更加专业的术语:对象监视器。

2、同步方法

  1. 格式
修饰符 synchronized 返回值类型 方法名称(参数列表...){
	...
}
  1. 注意事项
    (1)所有操作共享数据的代码都在{}中间添加一个
    (2)同步方法的锁对象就是this

3、使用Lock接口

  1. 方法:
    abstract void lock​() 获得锁。
    abstract void unlock​() 释放锁。

  2. 实现类
    java.util.concurrent.locks.ReentrantLock ,空参构造函数

  3. 注意事项:
    释放锁的动作必须被执行。

(4)实际案例

1、卖票案例分析

	(1)总共有3种途径卖票,每个途径,相当于一个线程对象
    (2)每个线程对象要执行的任务: 都是在卖票
    (3)3个线程对象,操作的资源 100 张票 是被共享的

2、解决策略:

(1) 定义实现类,实现Runnable接口

(2) 覆盖重写Runnable接口中的run方法.指定线程任务——卖票
(2.1)判断是否有票
(2.2)有: 出一张票
(2.3)票的数量减少1

(3) 创建Runnable接口的实现类对象

(4) 创建3个Thread对象,传递Runnable接口的实现类对象,代表,卖票的3种途径

(5) 3个Thread对象分别调用start方法,开启售票

3、代码实现

public class MyTicket implements Runnable{
    private int tickets= 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true){
//            sellTicketB();
            sellTicketA();
        }
    }

	// 同步函数
    private synchronized void sellTicketA(){
        if(tickets>0){
            System.out.println(Thread.currentThread().getName() + " 卖出第" + tickets-- + "张票");
        }else {
            return;
        }
    }
	
	//同步进程快
    private void sellTicketB() {
        synchronized(obj){
            if(tickets>0){
                System.out.println(Thread.currentThread().getName() + " 卖出第" + tickets-- + "张票");
            }else {
                return;
            }
        }
    }
}
public class synchronizedTest {
    public static void main(String[] args) {
        MyTicket task = new MyTicket();
		// 三个线程任务来出票
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

4、线程同步的原理

  1. 线程执行的前提:
    (1)cpu资源
    (2)锁对象

  2. 基本规则:
    线程对象执行同步代码块中的内容,要么全部执行,要么全部不执行,不能够被其他线程干扰。

  3. 拿买票案例举例说明
    在这里插入图片描述
    现在存在t0、t1和t2三个线程。
    假设一:
    假设t0线程获取cpu资源,执行线程任务遇到同步代码块,判断是否具有锁对象。
    有:获取锁对象
    进入同步代码块,执行同步代码,,假设t0在执行过程中没有被t1或者t2抢夺cpu资源,那么t0或顺利执行完同步代码块内代码,退出同步代码块,释放锁资源,继续和其他线程抢夺cpu资源和锁对象。

假设二:
假设t0线程获取cpu资源,执行线程任务遇到同步代码块,判断是否具有锁对象
有:获取锁对象
进入同步代码块,执行同步代码,假设t0在执行过程中被t1抢夺了cpu资源,那么t0线程将不能继续执行。t1线程执行任务,遇到同步代码块,判断是否具有锁对象,因为锁已经被t0拿了,因此t1进入阻塞状态,等待获取锁对象被释放。

假设三
假设t0执行完成了同步代码块的内容,释放了锁对象,t1处于阻塞状态,但此时t2线程抢到了cpu资源,执行代码到同步代码块,然后顺利获取锁对象,进入同步代码块执行。这种情况下,t1将继续等待t2在同步代码块执行完毕,然后再去抢夺cpu资源和锁资源。

可以发现,线程如果不进行调度的管理可能会出现长时间等待的问题,因为抢占式调度具有随机性,不能获得最大的性能。

(持续更新…)

四、线程状态

java.lang.Thread.State给出了六种线程状态

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动,还没有调用start方法
Runnable(可运行) 线程可以再java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于cpu
Blocked(锁阻塞) 当一个线程试图获取一个独享锁,而该对象被其他的线程持有,则该线程进入Blocked状态;当该对象持有锁时,该线程将变成Runnable状态
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

在这里插入图片描述
注意事项(一)

  1. sleep方法可以在同步中使用
  2. sleep方法可以在非同步中使用
  3. sleep方法与锁对象无关(不会释放锁)

注意事项(二)

  1. Object类定义waitnotify方法
  2. 因此任意对象可以调用wait()notify()方法
  3. 锁对象可以是任意的
  4. 但是锁对象必须使用在同步中,因此waitnotify方法必须在同步中使用

案例分析

双线程交替执行。有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 
{10,5,20,50,100,200,500,800,2,80,300,3000}; 
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从抽奖池中完成抽奖。

两个线程轮流交替抽奖,每抽出一个奖项就打印出来。
【输出示例】
	抽奖箱1...抽出了10元...
	抽奖箱2...抽出了20元...
	抽奖箱1...抽出了50元...
	抽奖箱2...抽出了800元...
	... ...
每次抽的过程中,不打印,抽完时一次性打印。
【输出示例】
	在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,5,20,50,100,200最高奖项为200元,总计额为385元
	在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:500,800,2,80,300,3000最高奖项为3000元,总计额为4682元
	在此次抽奖过程中,抽奖项2中产生了最高奖项,该最高奖项为3000元

1. 分析

两个线程的任务都是抽奖,因此很明显只需要定义一个线程任务对象“抽奖”即可。由于两个抽奖箱共享一个奖池,且要求两个抽奖箱交替进行,很明显需要用到线程的等待(wait)和唤醒(notify)操作。因此,两个线程对于奖池中奖金的操作需要同步。前面已经说明,线程任务只有一个,故同步代码块的锁对象使用线程任务对象自身(this)即可。

2、实现思路

在这里插入图片描述

3、代码实现

public class RunnableImpl implements Runnable{
    private List list;
    private Map<String,List> mp = new HashMap<>();
    private int count = 0;


    public RunnableImpl(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        List subList = new ArrayList();
        while (true){
            synchronized (this){
                if(list.size()<=0){
                    this.notifyAll();
                    mp.put(Thread.currentThread().getName(),subList);
                    count++;
                    if(count == 2){
                        Integer max = 0;
                        String max_name = "";
                        for (Map.Entry<String, List> entry : mp.entrySet()) {
                            List t = entry.getValue();
                            String s = entry.getKey();
                            Integer tmax = 0;
                            Integer sum = 0;
                            StringBuilder sb = new StringBuilder();
                            for (Object o : t) {
                                sb = sb.append(o).append(",");
                                sum += (Integer)o;
                                tmax = tmax < (Integer)o ? (Integer)o : tmax;
                            }
                           if(max < tmax){
                               max = tmax;
                               max_name = s;
                           }
                            //sb = sb.deleteCharAt(sb.length()-1);
                            String seq =sb.toString();
                            System.out.println("在此次抽奖过程中,"+ s +"总共产生了"+t.size()+"个奖项,分别为:" + seq +"最高奖项为"+ tmax +"元,总计额为"+ sum +"元");
                        }
                        System.out.println("在此次抽奖过程中,"+max_name+"中产生了最高奖项,该最高奖项为"+max+"元");
                    }
                    break;
                }
                if(list.size() > 0){
                    Object remove = list.remove(new Random().nextInt(list.size()));
                    System.out.println(Thread.currentThread().getName() + "..." + "抽出了"+ remove +"元...");
                    subList.add(remove);

                    this.notify();
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList(Arrays.asList(10,5,20,50,100,200,500,800,2,80,300,3000));
        RunnableImpl runnable = new RunnableImpl(list);
        new Thread(runnable,"抽奖箱1").start();
        new Thread(runnable,"抽奖箱2").start();
    }
}

五、生产者消费者模式

案例分析

包子铺卖包子,吃货吃饱做,包子铺包一个包子,吃货吃一个包子。

1、图解分析

在这里插入图片描述

2、多个线程间的通信

多个不同任务的线程要操作共享数据,一定需要统一的协调,也就是说需要一个统一的锁对象。在这种情况下,不能直接使用线程自己(this)作为锁对象,因为线程任务不同,必然也是各自独立的。因此,这个场景下,选择了盘子(List对象)来作为锁,因为他是全局唯一个,和生产者与消费者都有着密切关系。

3、代码实现

生产者

/*
包子铺:
1.判断盘子中有没有包子
2.无:生产一个包子,直到装满盘子,唤醒等待锁的线程
3.有:释放锁
 */
public class Productor implements Runnable{
    private List list;
    private int count = 0;

    public Productor(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            synchronized (list){
                try {
                    //盘子有了包子,进入等待
                    if(list.size() > 0){
                        list.wait();
                    }
                    //没有进入等待说明盘子空了,开始制作包子
                    String bz ="皮多肉韭菜鸡蛋包子";
                    list.add(bz);
                    System.out.println(Thread.currentThread().getName() + " 包了" + bz + count++);
                    // 唤醒吃货吃包子
                    list.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者

/*
吃货:
1.看盘子里有没有包子
2.有:吃掉
3.无:释放锁,唤醒正在等待锁的包子铺接着做包子
 */
public class Consumer implements Runnable{
    private List list;
    private int count=0;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (list) {
                // 盘子中没有包子了,进入等待阶段
                if(list.size() == 0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 没有进入等待说明盘子里还有包子
                while (list.size()>0){
                    String bz = (String)list.remove(0) + count++;
                    System.out.println(Thread.currentThread().getName() + "吃了" +  bz);
                }

                // 包子被吃完了,唤醒袍子铺做包子
                list.notify();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Productor productor = new Productor(list);
        Consumer consumer = new Consumer(list);
        Thread t0 = new Thread(productor);
        t0.setName("庆丰包子铺");
        t0.start();
        Thread t1 = new Thread(consumer);
        t1.setName("吃货A");
        t1.start();
        Thread t2 = new Thread(consumer);
        t2.setName("吃货B");
        t2.start();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38708854/article/details/106957789