多线程快速入门

多线程

一:线程和进程

1.1:线程和进程的概述

1. 进程:是内存中运行的一个个软件(比如:QQ, 微信)。
2. 线程:进程内部的一个独立执行单元。堆空间共享,栈空间是独立。

1.2:并发和并行的概述

1. 并行:在一个时间点同时发生多个事件。
2. 并发:在同一个时间段同时发生多个事件。

二:多线程实现

2.1:继承Thread类

代码的具体实现

public class MyThread extends Thread{
	/*
	 * 利用继承中的特点 
	 * 将线程名称传递  进行设置
	 */
	public MyThread(String name){
		super(name);
	}
	/*
	 * 重写run方法
	 *  定义线程要执行的代码
	 */
	public void run(){		
		for (int i = 0; i < 20; i++) {
			//getName()方法 来自父亲
			System.out.println(getName()+i);
		}
	}
}

public class Demo {
    public static void main(String[] args) {
      	System.out.println("这里是main线程");
    	MyThread mt = new MyThread("小强");    	
    	mt.start();//开启了一个新的线程
    	for (int i = 0; i < 20; i++) {
			System.out.println("旺财:"+i);
		}
	}
}

2.2:实现Runnable接口

  1. 基本实现
public class MyRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}

public class Demo {
    public static void main(String[] args) {
        //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "小强");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("旺财 " + i);
        }
    }
}	
  1. 匿名内部类
public static void main(String[] args) {
        //使用匿名内部类方式创建Runnable实例
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("输出"+i);
                }
            }
        });
        t1.start();
        // lambda 表达式简化语法
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                System.out.println("输出"+i);
            }
        });
        t2.start();
}

2.3:实现Callable接口

/**
 * 实现Callable的接口
 * 实现一个带返回值的任务
 * @创建日期 2020/3/18 15:31
 **/
public class Thread03_create03 {
    public static void main(String[] args) {
        //FutureTask包装我们的任务,FutureTask可以用于获取执行结果
        FutureTask<Integer> ft = new FutureTask<>(new MyCallable());

        //创建线程执行线程任务
        Thread thread = new Thread(ft);
        thread.start();
        try {
            //得到线程的执行结果
            Integer num = ft.get();
            System.out.println("得到线程处理结果:" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    // 实现Callable接口,实现带返回值的任务
    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int num = 0;
            for (int i = 0; i < 1000; i++) {
                System.out.println("输出"+i);
                num += i;
            }
            return num;
        }
    }
}

三:Thread类方法

3.1:基本方法

a:构造方法
Thread();
Thread(Sting name);
Thread(Runnable myRunnable);
Thread(Runnable myRunnable,Sting name);
b:常用方法
getName();    //获取线程的名字
start();      //开启一个线程
c:静态方法
static sleep(int 毫秒值);
static currentThread();

四:多线程内存图

4.1:内存流程图

在这里插入图片描述

4.2:总结:(多线程在内存中的执行顺序)

	第一:JVM运行程序,主方法进栈,从上往下依次执行。当运行到创建线程对象的时候,在堆中创建一个线程对象,根据
对象地址调用start方法,执行完毕后,在栈内存中创建一个新的栈,并把run()方法压到新的栈进行运行(两个栈互不影
响),CPU根据自己的爱好执行任意的栈。run()方法在新的栈运行。说白了就是。开启一个新的栈,把业务逻辑放到新的栈运行。

五:线程安全问题

5.1:为什么会出现线程安全问题

	存在着两个或者两个以上的线程,多个线程共享了着一个资源,而且对资源进行操作。

5.2:解决线程安全

5.2.1:同步代码块(synchronized);
public class Ticket implements Runnable{
	private int ticket = 100;
	Object lock = new Object();
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			synchronized (lock) {
				if(ticket>0){//有票 可以卖
					//出票操作
					//使用sleep模拟一下出票时间 
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//获取当前线程对象的名字 
					String name = Thread.currentThread().getName();
					System.out.println(name+"正在卖:"+ticket--);
				}
			}
		}
	}
}
5.2.2:同步方法(synchronized);
public class Ticket implements Runnable{
	private int ticket = 100;
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			sellTicket();
		}
	}
	/*
	 * 锁对象 是 谁调用这个方法 就是谁 
	 *   隐含 锁对象 就是  this
	 */
	public synchronized void sellTicket(){
        if(ticket>0){//有票 可以卖	
            //出票操作
            //使用sleep模拟一下出票时间 
            try {
              	Thread.sleep(100);
            } catch (InterruptedException e) {
              	// TODO Auto-generated catch block
              	e.printStackTrace();
            }
            //获取当前线程对象的名字 
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在卖:"+ticket--);
        }
	}
}
5.2.3:Lock琐;
public class Ticket implements Runnable{
	private int ticket = 100;
	Lock lock = new ReentrantLock();
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			lock.lock();
			if(ticket>0){//有票 可以卖
				//出票操作 
				//使用sleep模拟一下出票时间 
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//获取当前线程对象的名字 
				String name = Thread.currentThread().getName();
				System.out.println(name+"正在卖:"+ticket--);
			}
			lock.unlock();
		}
	}
}

5.3:总结

a: 线程安全问题:
	在堆中有一个变量,有多个线程堆的变量进行加减。一个线程获取了变
量,另外一个线程突然获取CPU,执行自己的线程,获取一个变量,然后对变
量值进行改变,突然原线程抢到了CPU,执行自己的线程,然后改变变量值。
相当于一个变量被执行两次操作,和预期的不也一样,从而造成线程安全问题.
b:解决线程安全问题:
	就是在把变量的获取和变量的改变这段程程序变为一个原子性,加锁。

六:线程间通信

6.1:线程间通信

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

6.2:等待唤醒机制的代码

//包子类
public class BaoZi {
    private boolean flag;
    
    public boolean isFlag() {
        return flag;
    }

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

//包子铺
public class BaoZiPu implements Runnable {
    BaoZi baozi ;
    public BaoZiPu() {
    }
    public BaoZiPu(BaoZi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (baozi) {
                if (!baozi.isFlag()) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子做好了");
                    baozi.setFlag(true);
                    baozi.notify();
                } else {
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

//吃货类
public class ChiHuo implements Runnable {
    BaoZi baozi;
    public ChiHuo() {
    }

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

    @Override
    public void run() {
        while(true) {
            synchronized (baozi) {
                if (baozi.isFlag()) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子吃完啦。");
                    baozi.setFlag(false);
                    baozi.notify();
                } else {
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

4.总结(等待唤醒机制的描述)

前提:同一把锁,俩个线程不同的业务逻辑,共享一个资源(根据更改状态,来改变
	  线程的醒和睡)

过程(比如做包子和吃包子):根据包子的有无,来分别让做包子和吃包子执行休息
和唤醒。如果有包子,做包子抢到了就让他休眠,如果吃货抢到了就他吃,并把包子
状态变为无,吃完唤醒。如果没有包子的状态下,吃包子的抢到就叫他休眠。做包子
的抢到就让他做包子,把包子状态改为有,唤醒吃包子的。(释放琐,就是在同步代
码块执行完毕。)

七:线程状态

7.1:线程状态的六种

创建状态  运行状态  阻塞状态  等待状态  计时等待状态  中止状态

7.2:线程六种状态的转化

在这里插入图片描述

7.3:六种状态之间的转化文字描述

	创建状态:创建多个线程。线程当得到琐进入运行状态,
没有得到琐进入堵塞状态。线程调用sleep()进行计时等待状
态。计时结束,得到琐,进行运行状态。没有得到琐,进入
堵塞状态。线程调用wait()方法进入等待状态,别的线程调用
唤醒机制。等待状态就被唤醒,继续执行该线程,或者进入
堵塞状态。线程执行完,进入中止状态。

八:线程池

8.1:线程池是什么

容纳多个线程的容器,线程可以重复使用,不需要重复的创建线程对象。

8.2:为什么使用线程池

a:节省资源消耗:线程池中的线程能被反复的使用,无需重复的创建线程对象。
b:提高效率:当线程任务来的时候,无需等到创建线程对象,直接使用线程。
c.便于线程管理:可以根据系统的承受能力,来决定线程池中的线程条数。

8.3:线程池的代码实现

概述:
a:Executors:是线程池的根接口,但是我们在开发时候用的接口为Executors的子接口ExecutorService。
b:创建一个任务对象。
c:用线程池工厂来创造一个线程池
d:提交任务
代码:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}


public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        // 创建Runnable实例对象
        MyRunnable r = new MyRunnable();

        //自己创建线程对象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 调用MyRunnable中的run()

        // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
        // 再获取个线程对象,调用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
        // 将使用完的线程又归还到了线程池中
        // 关闭线程池
        //service.shutdown();
    }
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值