线程简介
任务?
程序?
进程 Process?执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位
线程 Thread?一个进程中可以包含若干个线程,进程中至少有一个线程。线程是CPU调度和执行的单位
模拟多线程
线程实现(重点)
线程的创建
一、Thread类
1、使用方法
1.自定义线程类继承Thread类
2.重写run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程
2、代码示例
- //创建线程方式一:继承Thread类,重写run()方法,调用start()开启线程
- public class TestThread1 extends Thread{
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- System.out.println("Thread----:"+i);
- }
- }
- public static void main(String[] args) {
- //main线程,主线程
- //创建一个线程对象
- TestThread1 testThread1=new TestThread1();
- //调用方法开启线程
- testThread1.start();
- for (int i = 0; i < 10; i++) {
- System.out.println("Main--------:"+i);
- }
- }
- }
复制代码 3、运行结果
- Main--------:0
- Main--------:1
- Thread----:0
- Thread----:1
- Main--------:2
- Thread----:2
- Main--------:3
- Main--------:4
- Main--------:5
- Main--------:6
- Thread----:3
- Main--------:7
- Main--------:8
- Main--------:9
- Thread----:4
- Thread----:5
- Thread----:6
- Thread----:7
- Thread----:8
- Thread----:9
复制代码 4、小结
线程开启不一定立即执行,而是由CPU选择调度执行
5、案例
下载图片- //练习Thread,实现多线程同步下载图片
- public class TestThread2 extends Thread{
- private String url;
- private String name;
- public TestThread2(String url, String name) {
- this.url = url;
- this.name = name;
- }
- @Override
- public void run() {
- WebDownloader webDownloader = new WebDownloader();
- webDownloader.downloader(url,name);
- System.out.println("下载了文件名为:"+name);
- }
- public static void main(String[] args) {
- TestThread2 testThread2_1 = new TestThread2("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO1.jpg");
- TestThread2 testThread2_2 = new TestThread2("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO2.jpg");
- TestThread2 testThread2_3 = new TestThread2("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO3.jpg");
- testThread2_1.start();
- testThread2_2.start();
- testThread2_3.start();
- }
- }
- class WebDownloader{
- public void downloader(String url,String name) {
- try {
- FileUtils.copyURLToFile(new URL(url),new File(name));
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("IO异常");
- }
- }
- }
复制代码 二、Runnable接口
1、使用方法
1.定义TestThread3类实现Runnable接口
2.实现run()方法,编写线程执行体
3.创建线程对象,传入testThread3对象,调用start()方法启动线程
代理方法
2、代码示例
- public class TestThread3 implements Runnable{
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- System.out.println("run:"+i);
- }
- }
- public static void main(String[] args) {
- TestThread3 testThread3=new TestThread3();
- new Thread(testThread3).start();
- for (int i = 0; i < 10; i++) {
- System.out.println("main:"+i);
- }
- }
- }
复制代码 3、运行结果
- main:0
- run:0
- main:1
- run:1
- main:2
- run:2
- main:3
- run:3
- main:4
- run:4
- main:5
- run:5
- main:6
- run:6
- main:7
- run:7
- main:8
- run:8
- main:9
- run:9
复制代码 4、对比
继承Thread类
子类继承Thread类具备多线程能力
通过子类对象.start() 启动
不建议使用:避免OOP单继承局限性
实现Runnable接口
实现接口Runnable具有多线程能力
传入目标对象+Thread对象.start() 启动 代理模式 一份资源多个代理- StartThread station=new StartThread();
- new Thread(station,"小明").start();
- new Thread(station,"小红").start();
- new Thread(station,"小刚").start();
复制代码 建议使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
5、初识并发问题
购买车票场景问题
代码- //多个线程同时操作同一个对象
- //买火车票的例子
- public class TestThread4 implements Runnable{
- private int ticketNums=10;
- @Override
- public void run() {
- while (true){
- if (ticketNums<=0){
- break;
- }
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+":拿到了第"+ticketNums--+"张票");
- }
- }
- public static void main(String[] args) {
- TestThread4 testThread4=new TestThread4();
- new Thread(testThread4,"1号线程").start();
- new Thread(testThread4,"2号线程").start();
- new Thread(testThread4,"3号线程").start();
- }
- }
复制代码 5、线程优先级
Java提供一个线程调度器来监控监视程序中启动后进入到就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用数字表示,范围从1~10
- 通过getPriority().setPriority(int xxx)
- 1号线程:拿到了第10张票
- 3号线程:拿到了第9张票
- 2号线程:拿到了第8张票
- 2号线程:拿到了第6张票
- 3号线程:拿到了第7张票
- 1号线程:拿到了第6张票
- 1号线程:拿到了第5张票
- 2号线程:拿到了第5张票
- 3号线程:拿到了第4张票
- 3号线程:拿到了第3张票
- 2号线程:拿到了第1张票
- 1号线程:拿到了第2张票
复制代码优先级高的不一定先跑
6、守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 例如:后台记录操作日志,监控内存,垃圾回收
- //模拟龟兔赛跑
- public class Race implements Runnable{
- private static String winner;
- @Override
- public void run() {
- for (int i = 0; i <=100; i++) {
- if (Thread.currentThread().getName()=="兔子"&&i%10==0){
- try {
- Thread.sleep(2 );
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- boolean flag=gameOver(i);
- if(flag) break;
- System.out.println(Thread.currentThread().getName()+"跑了:"+i+"步");
- }
- }
- private boolean gameOver(int steps){
- if (winner!=null){
- return true;
- }else {
- if (steps==100){
- winner=Thread.currentThread().getName();
- System.out.println("winner is"+winner);
- return true;
- }
- }
- return false;
- }
- public static void main(String[] args) {
- Race race=new Race();
- new Thread(race,"兔子").start();
- new Thread(race,"乌龟").start();
- }
- }
复制代码 线程同步(重点)
并发:多个线程操作同一个资源
排队:多个线程访问同一个对象,并且某些线程还想修改这个对象,这时我们就需要线程同步,线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
由于同一个进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程被挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
购买车票场景问题2
- public class TestCallable implements Callable<Boolean> {
- //实现call方法,需要抛出异常
- @Override
- public Boolean call() throws Exception {
- WebDownloader webDownloader = new WebDownloader();
- webDownloader.downloader(url,name);
- System.out.println("下载了文件名为:"+name);
- return true;
- }
- private String url;
- private String name;
- public TestCallable(String url, String name) {
- this.url = url;
- this.name = name;
- }
-
- public static void main(String[] args) {
- //创建目标对象
- TestCallable testThread2_1 = new TestCallable("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO11.jpg");
- TestCallable testThread2_2 = new TestCallable("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO21.jpg");
- TestCallable testThread2_3 = new TestCallable("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO31.jpg");
- //创建执行服务
- ExecutorService ser= Executors.newFixedThreadPool(3);
- //提交执行
- Future<Boolean> submit1 = ser.submit(testThread2_1);
- Future<Boolean> submit2 = ser.submit(testThread2_2);
- Future<Boolean> submit3 = ser.submit(testThread2_3);
- //获取结果
- try {
- boolean rs1 = submit1.get();
- boolean rs2 = submit2.get();
- boolean rs3 = submit3.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- //关闭服务
- ser.shutdownNow();
- }
- }
复制代码 synchronized与Lock的对比
- Lock是显式锁(手动开启和关闭锁,try开启,finally关闭),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将使用较少时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)
- 优先使用顺序:Lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体外)
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占用的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情景。某一个同步块同时拥有“两个以上对象的锁”时,就可能发生“死锁问题”- public class StacticProxy {
- public static void main(String[] args) {
- WeddingCompany weddingCompany=new WeddingCompany(new You());
- weddingCompany.HappyMarry();
- }
- }
- interface Marry{
- void HappyMarry();
- }
- //真实角色
- class You implements Marry{
- @Override
- public void HappyMarry() {
- System.out.println("marry");
- }
- }
- //代理角色
- class WeddingCompany implements Marry{
- private Marry target;
- public WeddingCompany(Marry target) {
- this.target = target;
- }
- @Override
- public void HappyMarry() {
- before();
- this.target.HappyMarry();
- after();
- }
- private void after() {
- System.out.println("收尾款");
- }
- private void before() {
- System.out.println("布置现场");
- }
- }
复制代码 产生死锁的四个必要条件
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
只要破坏其中任意一个或多个就可以避免死锁发生
线程通信问题
生产者消费者问题
- 仓库中只能存放一件物品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库再次放入产品为止
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间互相依赖,互为条件
关键方法wait()、notify()
wait:线程释放锁等待
notify:唤醒一个等待状态的线程
管程法
- public interface Runnable{
- public abstract void run();
- }
复制代码 信号灯法
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具
好处
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
(1)corePoolSize:核心池大小
(2)maximumPoolSize:最大线程数
(3)keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API
ExecutorService和Executors
1.ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
(1)void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
(2)Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
(3)void shutdown():关闭线程池
2.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |