java并发01-线程的基本概念(八千字详解)

33 篇文章 1 订阅

在这里插入图片描述
线程的基本概念:
在这里插入图片描述

线程是系统不同的执行路径,下面是一条执行路径:
在这里插入图片描述
每一个分支都是一个线程,之前写的程序都是一个分支。
进程 :一个程序就是一个进程 将代码放入代码区,称为进程
操作系统调度程序
线程是一个动态的概念:在进程内的多条执行路径
机器中实际运行的都是线程
机器可以同时执行多个线程,Windows支持多线程,也支持多进程(DOS是单进程)
实际上是CPU轮转执行多个线程,速度较快,所以可以看做CPU同时执行多个线程
但在某一个时间点上,CPU只能执行一个线程.
如果是多个CPU,可以真正执行多线程
注意CPU分的时间片不是一定平均的,可能这个线程时间多一点,另一个线程时间少一点。
一个进程的线程共享内存单元(进程独享内存),这是就会产生冲突,并发问题。
在这里插入图片描述
在这里插入图片描述
进程和线程的本质区别是每个进程拥有自己的一整套变量,而线程则共享数据

创建线程的方法:

## Runnable接口
在这里插入图片描述

  1. 自定义一个线程,实现Runnable接口的run方法
    run方法就是要执行的内容,会在另一个分支上进行
    Thread类本身也实现了Runnable接口
  2. 主方法中new一个自定义线程对象,然后new一个Thread类对象,其构造方法的参数是自定义线程对象
    (该对象实现了run方法,Thread也实现了run方法,所以这是方法的重写)
    在这里插入图片描述
    参数类型是Runnable接口,也接受其子类(自定义的线程对象)
    (父类引用指向子类对象 有多态的过程)
  3. 执行Thread类的start方法,线程开始执行
    自此产生了分支,一个分支会执行run方法,在主方法中不会等待run方法调用完毕返回才继续执行,而是直接继续执行,是第二个分支。这两个分支并行运行
    输出时会执行一会一个分支,再执行一会另一个分支(与Cpu的工作方式一致)
    如果不new一个线程Thread对象调用start方法,而直接调用自定义线程的run方法
    那么只是方法调用,仍为单线程,而不是多线程并行执行

①自定义一个类MyThread,实现Runnable接口的run方法 implements Runnable
②在main方法中new一个自定义类 new MyThread mt
③在main方法中new一个Thread类,其构造方法参数是自定义类的对象,然后调用start方法(会自动调用run方法)
写在一行就是 Thread t = new Thread(new MyThread ()).start();
示例:

public class test {
	public static void main(String []args){
		Runner r = new Runner();
		Thread t = new Thread(r);
		t.start();
		for(int i = 0; i <100;i++){
			System.out.println("Main " +i);
		}
	}
}
class Runner implements Runnable {
	public void run (){
		for(int i = 0; i<100; i++){
			System.out.println("Runner"+i);
		}
	}
}

输出结果:
在这里插入图片描述
两个线程交替执行
实际上采用了静态代理模式
定义
代理模式(Proxy Pattern)是对象的结构型模式,代理模式给某一个对象提供了一个代理对象,并由代理对象控制对原对象的引用。
代理模式不会改变原来的接口和行为,只是转由代理干某件事,代理可以控制原来的目标,例如:代理商,代理商只会买东西,但并不会改变行为,不会制造东西。
分类
静态代理和动态代理
在这里插入图片描述
接口

package com.liang.pattern;  
public interface UserManager {  
    public void addUser(String userId,String userName);  
    public void delUser(String userId);  
    public void modifyUser(String userId,String userName);  
    public String findUser(String userId);  
}  

目标对象

package com.liang.pattern;  
public class UserManagerImpl implements UserManager {  
    public void addUser(String userId, String userName) {  
        try{  
            System.out.println("UserManagerImpl.addUser() userId-->>" + userId);  
        }catch(Exception e){  
            e.printStackTrace();  
              
            throw new RuntimeException();  
        }  
    }  
  
    public void delUser(String userId) {  
        System.out.println("UserManagerImpl.delUser() userId-->>" + userId);  
    }  
    public String findUser(String userId) {  
        System.out.println("UserManagerImpl.findUser() userId-->>" + userId);  
        return "于亮";  
    }  
    public void modifyUser(String userId, String userName) {  
        System.out.println("UserManagerImpl.modifyUser() userId-->>" + userId);  
    }  
}  

代理类,我们使用代理对象做一些日志记录,我们将简略的信息打印到控制台。

package com.liang.pattern;  
  
public class UserManagerImplProxy implements UserManager {  
    private UserManager userManager;  
    public UserManagerImplProxy(UserManager userManager){  
        this.userManager = userManager;  
    }  
    public void addUser(String userId, String userName) {  
                //记录日志等操作或打印输入参数  
        System.out.println("start-->>addUser() userId-->>" + userId);  
        try{  
            userManager.addUser(userId, userName);  
                //执行成功,打印成功信息  
            System.out.println("success-->>addUser()");  
        }catch(Exception e){  
            e.printStackTrace();  
                //失败时,打印失败信息  
            System.out.println("error-->>addUser()");  
            //throw new RuntimeException();  
        }  
    }  
  
    public void delUser(String userId) {  
        //同上,略  
        userManager.delUser(userId);  
    }  
  
    public String findUser(String userId) {  
        //同上,略  
        userManager.findUser(userId);  
        return null;  
    }  
  
    public void modifyUser(String userId, String userName) {  
        //同上,略  
        userManager.modifyUser(userId, userName);  
          
    }  
}  

客户端调用

package com.liang.pattern;  
public class Client {  
  
    public static void main(String[] args) {  
        UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());  
        userManager.addUser("001","sure");  
    }  
}   

输出结果,此方法执行成功
start–>>addUser() userId–>>001
UserManagerImpl.addUser() userId–>>001
success–>>addUser()

从类图我们可以看出,客户端本来可以直接和目标对象打交道,代理中间加了一个间接层,他们实现的功能是一样的,也没有改变参数。

优缺点
优点:
1、直观感受,静态代理是实实在在的存在的,我们自己写的。
2、在编译期加入,提前就指定好了谁调用谁,效率高。

缺点:
同样,它的优点也成了它致命的缺点。
1、静态代理很麻烦,需要大量的代理类当我们有多个目标对象需要代理时,我就需要建立多个代理类,改变原有的代码,改的多了就很有可能出问题,必须要重新测试。
2、重复的代码会出现在各个角落里,违背了一个原则:重复不是好味道,我们应该杜绝一次次的重复。
3、在编译期加入,系统的灵活性差
在这里插入图片描述
实际上方法1就采用了静态代理模式
Thread类和自定义线程类都实现了Runnable接口
Thread类是代理Proxy,自定义线程类是被代理类
Thread 构造方法
在这里插入图片描述
在这里插入图片描述通过调用Thread的start方法,实际上调用了自定义线程类的start方法(当然除此之外还有其他的代码)
## 继承Thread类
①自定义一个类MyThread,继承Thread类,重写run方法
②在main方法中new一个自定义类,然后直接调用start方法

public class test {
	public static void main(String []args){
		Runner r = new Runner();
		r.start();
		for(int i = 0;i <100;i++){
			System.out.println("Main"+i);
		}
	}
}
class Runner extends Thread {
	public void run (){
		for(int i = 0; i<100;i++){
			System.out.println("Runner"+i);
		}
	}
}

两个方法比较而言第二个方法代码量较少
但是第一个方法比较灵活,自定义线程类还可以继承其他的类,而不限于Thread类
推荐使用接口来实现多线程(第1种方法)
在这里插入图片描述
## 实现Callable
在这里插入图片描述
在这里插入图片描述
类型参数:
V - call 方法的结果类型(返回值类型)
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
在这里插入图片描述
在这里插入图片描述
Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。
可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。
获取ExecutorService实例的方法需要借助Executors类的静态方法
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
类型参数:
V - 此 Future 的 get 方法所返回的结果类型(需要指定)
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
在这里插入图片描述
可以通过ExecutorService 的submit方法来获取Future对象。
在这里插入图片描述
submit方法的参数是自定义进程类的对象
然后调用Future接口的get方法获取返回值对象

如果想停止线程,那么调用ExecutorService的shutdownNow方法

即使run方法的代码执行完毕,如果没有调用shutdownNow方法,那么线程仍然没有停止。
而使用方法1的话,代码执行完毕线程即结束
示例:龟兔赛跑:

public class T1 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Race bunny = new Race("兔",300);
		Race tortoise = new Race("龟",1000);
		ExecutorService es = Executors.newFixedThreadPool(2);
		Future<Integer> result1 = es.submit(bunny);
		Future<Integer> result2 = es.submit(tortoise);
		
		Thread.sleep(2000);
		bunny.setFlag(false);
		tortoise.setFlag(false);
		
		int bunnySteps = result1.get();
		int tortoiseSteps = result2.get();
		System.out.println("兔子跑了"+bunnySteps+"步");
		System.out.println("乌龟跑了"+tortoiseSteps+"步");
		
		es.shutdownNow();
	}
}

class Race implements Callable<Integer>{
	private String name;
	private int step;
	private int time = 0 ;
	private boolean flag = true;
	
	public Integer call() throws Exception {
		while(flag){
			Thread.sleep(time);
			step++;
		}
		return step;
	}

	public Race() {
	}

	public Race(String name, int time) {
		this.name = name;
		this.time = time;
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java并发线程安全集合是指在多线程环境下能够保证数据一致性和线程安全的数据结构。Java提供了许多并发线程安全集合,包括ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList、CopyOnWriteArraySet等。 ConcurrentHashMap是一个线程安全的哈希表,它允许多个线程同时读取并修改其中的元素。它使用分段锁的方式来实现并发访问,不同的线程可以同时访问不同的分段,从而提并发性能。 ConcurrentSkipListMap是一个基于跳表的并发有序映射,它可以提供较好的并发性能,且支持按照键的顺序进行遍历。它的实现是通过通过多层链表实现的,每一层链表中的节点按照键的顺序排列。 ConcurrentSkipListSet是一个基于ConcurrentSkipListMap的并发有序集合,它实现了Set接口,并且保证元素的有序性和线程安全性。 CopyOnWriteArrayList是一个线程安全的ArrayList,它通过每次修改时创建一个新的副本来实现线程安全。虽然在插入和删除操作时需要复制整个数组,但读取操作非常效,适用于读操作远多于写操作的场景。 CopyOnWriteArraySet是一个线程安全的Set,它是基于CopyOnWriteArrayList实现的。它通过复制整个数组来实现线程安全,保证了元素的唯一性和线程安全。 这些并发线程安全集合在多线程环境中保证了数据的一致性和线程安全性,能够提并发性能和效率,适用于并发和需要频繁读写的场景。但需要注意的是,并发集合在某些操作上可能会损失一些性能,因此在选择使用时需根据具体需求进行权衡和选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值