线程的基本概念:
线程是系统不同的执行路径,下面是一条执行路径:
每一个分支都是一个线程,之前写的程序都是一个分支。
进程 :一个程序就是一个进程 将代码放入代码区,称为进程
操作系统调度程序
线程是一个动态的概念:在进程内的多条执行路径
机器中实际运行的都是线程
机器可以同时执行多个线程,Windows支持多线程,也支持多进程(DOS是单进程)
实际上是CPU轮转执行多个线程,速度较快,所以可以看做CPU同时执行多个线程
但在某一个时间点上,CPU只能执行一个线程.
如果是多个CPU,可以真正执行多线程
注意CPU分的时间片不是一定平均的,可能这个线程时间多一点,另一个线程时间少一点。
一个进程的线程共享内存单元(进程独享内存),这是就会产生冲突,并发问题。
进程和线程的本质区别是每个进程拥有自己的一整套变量,而线程则共享数据
创建线程的方法:
## Runnable接口
- 自定义一个线程,实现Runnable接口的run方法
run方法就是要执行的内容,会在另一个分支上进行
Thread类本身也实现了Runnable接口 - 主方法中new一个自定义线程对象,然后new一个Thread类对象,其构造方法的参数是自定义线程对象
(该对象实现了run方法,Thread也实现了run方法,所以这是方法的重写)
参数类型是Runnable接口,也接受其子类(自定义的线程对象)
(父类引用指向子类对象 有多态的过程) - 执行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;
}
}