1.什么是线程,它与进程的关系是什么,它的作用和意义是什么?
https://blog.csdn.net/hzr0523/article/details/83577246
2.怎么使用多线程,具体有哪些使用方法?
3.使用多线程后,带来的问题有哪些,解决方法有哪些?
本片文章将介绍第二个问题
一、线程的创建
1.继承Tread类
main()方法本就是一个线程,创建Thread线程是另开一个线程,与main()线程交替运行.
class TreadDemo extends Thread {
public void run() {
//写业务逻辑
for(int i = 0 ; i < 10; i ++){
System.out.println(Thread.currentThread().getName + i);
}
}
}
//线程启动
public static void main(String args[]) {
Thread th1 = new Thread();
Thread th2 = new Thread();
th1.start();
th2.start();
}
注意: 多线程的启动不能直接调用run()方法,因为直接调用run()方法,还是属于普通的方法调用,结果会按调用顺序依次输出,应当调用Thread类中提供的start方法,此方法里调用了本机的系统函数(用native关键字修饰的一个方法)
2.实现Runnable接口
class ThreadDemo implements Runnable{
public void run() {
for(int i=0; i<50;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
public class Test() {
public static void main(String args[]) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo);
Thread t2 = new Thread(threadDemo);
t1.start();
t2.start();
}
}
这种方法与第一种实现的效果是一致的,同时,由于我们关注的是run()方法中做的事情,没必要新建一个实现类,可以使用匿名内部类去实现线程的创建。
public class Test() {
public static void main(String[] args) {
//创建并启动线程
new Thread(){
public void run() {
for(int i = 0; i < 50; i ++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}.start();
}
}
由于现代计算机性能较好,少量数据可能看不出多线程之间的切换。可以加入延时,这样结果比较清楚。
写到这里,就要比较一下继承Thread类和实现Runnable接口的区别:
- 由于类是单继承,而接口可以多实现,因此,继承Thread类,限制了程序的可拓展性。
- Thread类在操作多线程的时候,无法达到资源共享的目的,而Runnable则可以实现资源共享。
public class ThreadDemo{
public static void main(String[] args) {
ThreadDemo1 t1 = new ThreadDemo1();
ThreadDemo1 t2 = new ThreadDemo1();
t1.start();
t2.start();
}
}
class ThreadDemo1 extends Thread{
private int ticket = 5;
public void run() {
for(int i = 0; i < 50; i ++) {
if(this.ticket > 0) {
ticket --;
System.out.println(Thread.currentThread().getName() + "--" + ticket);
}
}
}
}
运行结果:
*/
public class ThreadDemo{
public static void main(String[] args) {
// ThreadDemo1 t1 = new ThreadDemo1();
// ThreadDemo1 t2 = new ThreadDemo1();
// t1.start();
// t2.start();
ThreadDemo2 threadDemo2 = new ThreadDemo2();
Thread t1 = new Thread(threadDemo2);
Thread t2 = new Thread(threadDemo2);
t1.start();
t2.start();
}
}
class ThreadDemo2 implements Runnable{
private int ticket = 5;
public void run(){
for(int i = 0; i < 50; i ++) {
if(this.ticket > 0) {
ticket --;
System.out.println(Thread.currentThread().getName() + "--" + ticket);
}
}
}
}
运行结果:
可以看到,已经实现了资源共享,但是又出现了新问题,就是票卖的错乱了,这也是多线程带来的一个常见的并发问题。后面第三部分学习内容将会说明如何处理这种情况。
3.实现Callable接口
这个比较陌生,之前没有接触过,正好目前复习多线程知识,学习一下它的使用。
Callable中提供的是call()方法,作用类似于Runnable中的run()方法,但是call()可以有返回值,可以抛出异常。
public class ThreadDemo{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> threadCallable = new ThreadCallable();
FutureTask<Integer> futureTask = new FutureTask(threadCallable);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
class ThreadCallable implements Callable<Integer>{
private int result = 0;
@Override
public Integer call() throws Exception {
for(int i = 0; i < 10; i ++) {
result += 1;
System.out.println(Thread.currentThread().getName() + "----" + result);
}
return result;
}
}
这是一个简单的创建过程,其中使用到了FutureTask类。
FutureTask可以获取到异步计算的结果,其实现了RunnableFuture接口,而RunnableFuture接口也继承Runnable和Future接口。
其中比较的核心的是Future接口,其提供了5个方法(其实只有四个,有一个是重载)
cancel方法:此方法试图取消此任务的执行,如果任务已经完成或者已经被取消或者由于其他原因不可取消,则取消失败。如果此方法调用成功且任务在调用之前没有被启动,则此任务应当永远不执行,如果任务已经启动了,则根据参数mayInterruptIfRunning(boolean)来决定运行此任务的线程是否可以被打断。
isCancelled(): 判断任务是否中止。
isDone(): 判断任务是否完成。
get() : 等待知道计算完成,并返回结果
get(long timeout, TimeUnit unit):在给定时间内,如果计算完成,则返回结果,如果超时,抛出异常TimeoutException。
在上面的demo中,也使用了get()方法获取结果。
4.使用线程池
从JDK1.5之后,util包提供了ExcutorServicer线程池的实现,主要目的是为了重复利用线程,提高系统效率。Thread是一个重量级的资源,创建、启动以及销毁都是比较耗费系统资源的,因此对线程的重复利用是一种非常好的设计习惯。加之系统中的线程数量是有限的,且线程数量与系统性能是一种抛物线关系,也就是说当线程数量达到某个数值以后,性能反倒会降低很多。因此对线程的管理,尤其是对线程数量的控制,更能直接决定成程序的性能。
public class ThreadDemo{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建Callable对象,放入线程池
for(int i = 0; i < 5; i ++) {
Callable<Integer> threadCallable = new ThreadCallable();
executorService.submit(threadCallable);
}
//submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
//关闭线程池
//executorService.shutdown();
}
}
class ThreadCallable implements Callable<Integer>{
private int result = 0;
@Override
public Integer call() throws Exception {
for(int i = 0; i < 100; i ++) {
try{
Thread.sleep(100);
}catch (InterruptedException e) {
}
result += 1;
System.out.println(Thread.currentThread().getName() + "----" + result);
}
return result;
}
}
运行结果:
Executors提供了四种策略来创建线程池:
1.newFixedThreadPool:
创建一个可重用的固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "------ run");
}
};
for(int i = 0; i < 50; i ++) {
executorService.execute(runnable);
}
//submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
//关闭线程池
//executorService.shutdown();
}
2.newCachedThreadPool:
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时,将重用他们。
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "------ run");
}
};
for(int i = 0; i < 20; i ++) {
executorService.execute(runnable);
}
//submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
//关闭线程池
//executorService.shutdown();
}
可以看到thread-1线程被复用了,不许设置线程数量,根据需要创建。
3.newScheduledThreadPool:
创建一个线程池,他可以安排在给定延迟后运行或者定期的执行。
newScheduledThreadPool(int corePoolSize) :corePoolSize是核心线程数量
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "------ run");
}
};
//启动后5s第一次执行线程,之后每3s执行一次。
scheduledExecutorService.scheduleAtFixedRate(runnable, 5, 3,TimeUnit.SECONDS);
}
4.newSingleThreadExecutor:
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i = 0; i < 20; i ++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "------ run");
});
}
//submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中
//关闭线程池
//executorService.shutdown();
}