JAVA多线程并发(1)

1. JAVA多线程实现/创建方式

1.1 继承Thread类

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过条用Thread类的start()方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。

创建线程的步骤:

1 定义一个类继承Thread。

2 重写run方法。

3 创建子类对象,就是创建线程对象。

4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法

 

思考:

(1)创建线程的目的是什么?

是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。

(2)线程对象调用 run方法和调用start方法区别?

线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。

(3)为什么要继承Thread类,并调用其的start方法才能开启线程呢?

继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。

(4)那为什么不直接创建Thread类的对象呢?

Thread t1 = new Thread().start();

这样做没有错,但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

点击查看代码
package ThreadPackage;

import entity.User;

public class ThreadDemo extends Thread {
    private User user;

    public ThreadDemo() {

    }

    public ThreadDemo(User user) {
        this.user = user;
    }

    public void setUser(User user) {
        this.user = user;
    }


    @Override
    public void run() {
        System.out.println(this.getName() + "User's information : " + this.user + "," + this.getState());
    }

    public static void main(String[] args) {

        User user = new User(12, "zhangsan", "1234");
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.setUser(user);
        System.out.println(threadDemo.getState());
        System.out.println(threadDemo.getName());
        new Thread(threadDemo).start();


        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(threadDemo.getState());

        ThreadDemo thread2 = new ThreadDemo(user);
        thread2.start();
    }
}

获取当前线程和线程名称

  • currentThread()获取当前线程对象
  • currentThread().getName();获取当前线程对象的名称

 

1.2 实现Runnable接口

如果创建的目标类已经继承另一个类,此时就无法再继承Thread类,JAVA中不允许多继承。但是,可实现Runnable接口,然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程

创建线程的步骤。

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。。

3、创建Thread类的对象

4、Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

点击查看代码
package ThreadPackage;

import entity.User;

public class ThreadRunnableDemo implements Runnable {
    private User user;

    public ThreadRunnableDemo() {

    }

    public ThreadRunnableDemo(User user) {
        this.user = user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "User's information : " + this.user);
    }

    public static void main(String[] args) {
        User user = new User(12, "zhangsan", "1234");
        ThreadRunnableDemo thread1 = new ThreadRunnableDemo();
        thread1.setUser(user);
        new Thread(thread1).start();

        ThreadRunnableDemo thread2 = new ThreadRunnableDemo(user);
        new Thread(thread2).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(user);
            }
        }).start();
    }
}

 

思考:为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?

  1. 实现Runnable接口,避免了继承Thread类的单继承局限性。创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

 

思考:实现Runnable的好处

  1. 实现Runnable接口,避免了继承Thread类的单继承局限性

  2. 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。

 

1.3 线程匿名内部类使用

点击查看代码
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
	方式1:创建线程对象时,直接重写Thread类中的run方法
		new Thread() {
			public void run() {
				for (int x = 0; x < 40; x++) {
					System.out.println(Thread.currentThread().getName()
							+ "...X...." + x);
				}
			}
		}.start();

	方式2:使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法
		Runnable r = new Runnable() {
			public void run() {
				for (int x = 0; x < 40; x++) {
					System.out.println(Thread.currentThread().getName()
							+ "...Y...." + x);
				}
			}
		};
		new Thread(r).start();

1.3 实现Callable接口

有返回值的任务必须实现Callable接口,重写call方法。执行Callable任务后,可以获取一个Future对象,在改对象上调用get方法就可以获得Callable任务返回的Object。

2. 线程池

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

 

为什么使用线程池?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

线程池主要用来解决线程生命周期开销问题和资源不足问题通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

 

2.1 使用线程池方式执行Runnable接口

通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。        

  • Executors:线程池创建工厂类
    • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
  • ExecutorService:线程池类
    • Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

 

  • 使用线程池中线程对象的步骤:
    • 创建线程池对象
    • 创建Runnable接口子类对象
    • 提交Runnable接口子类对象
    • 关闭线程池

(1)定义一个对象实现Runnable接口,并重写Runnable方法

点击查看代码
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("教我游泳,交完后,教练回到了游泳池");
	}
}

(2)使用线程池方式执行Runnable接口

点击查看代码
public class ThreadPoolDemo {
	public static void main(String[] args) {
		//创建线程池对象
		ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
		//创建Runnable实例对象
		MyRunnable r = new MyRunnable();
		
		//从线程池中获取线程对象,然后调用MyRunnable中的run()
		service.submit(r);
		//再获取个线程对象,调用MyRunnable中的run()
		service.submit(r);
		service.submit(r);
        //注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中

        //关闭线程池
		//service.shutdown();
	}
}

2.2 使用线程池方式执行Callable接口

  • Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
  • ExecutorService:线程池类
    • <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

  • call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

  • 使用线程池中线程对象的步骤:
    • 创建线程池对象
    • 创建Callable接口子类对象
    • 提交Callable接口子类对象
    • 关闭线程池

(1)定义一个对象实现Callable接口实,并冲洗call()方法,call方法可抛出异常、返回线程任务执行完毕后的结果

点击查看代码
public class MyCallable implements Callable {
	@Override
	public Object call() throws Exception {
		System.out.println("我要一个教练:call");
		Thread.sleep(2000);
		System.out.println("教练来了: " +Thread.currentThread().getName());
		System.out.println("教我游泳,交完后,教练回到了游泳池");
		return null;
	}
}

(2)使用线程池方式执行Callable

点击查看代码
public class ThreadPoolDemo {
	public static void main(String[] args) {
		//创建线程池对象
		ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
		//创建Callable对象
		MyCallable c = new MyCallable();
		
		//从线程池中获取线程对象,然后调用MyRunnable中的run()
		service.submit(c);
		
		//再获取2个教练
		service.submit(c);
		service.submit(c);
		//注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中

		//关闭线程池
		//service.shutdown();
	}
}

 

*使用线程池计对两个数求和

(1)定义一个对象实现Callable接口,并重写callable方法

点击查看代码
public class MyCallable implements Callable<Integer> {
	//成员变量
	int x = 5;
	int y = 3;
	//构造方法
	public MyCallable(){
	}
	public MyCallable(int x, int y){
		this.x = x;
		this.y = y;
	}

	@Override
	public Integer call() throws Exception {
		return x+y;
	}
}

(2)创建线程池,并执行call()方法

点击查看代码
public class ThreadPoolDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建线程池对象
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		
		//创建一个Callable接口子类对象
		//MyCallable c = new MyCallable();
		MyCallable c = new MyCallable(100, 200);
		MyCallable c2 = new MyCallable(10, 20);
		
		//获取线程池中的线程,调用Callable接口子类对象中的call()方法(相当于Runable接口中的run()方法), 完成求和操作
		//<Integer> Future<Integer> submit(Callable<Integer> task)
		// Future 结果对象
		Future<Integer> result = threadPool.submit(c);
		//此 Future 的 get 方法所返回的结果类型
		Integer sum = result.get();
		System.out.println("sum=" + sum);
		
		//再演示
		result = threadPool.submit(c2);
		sum = result.get();
		System.out.println("sum=" + sum);
		//关闭线程池(可以不关闭)
		
	}
}

 

上一篇:多线程(上)


下一篇:java多线程学习 基础篇(二) Thread类和Runnable接口