http://ifeve.com/java-concurrency-thread-directory/
线程和进程
线程
表示进程中负责程序执行的执行单元,依靠程序进行运行。线程是程序中的顺序控制流,只能使用分配给程序的资源和环境。
进程
表示资源的分配和调度的一个独立单元,通常表示为执行中的程序。一个进程至少包含一个线程。
进程和线程的区别
- 进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
- 进程是资源分配和拥有的单位,而同一个进程内的线程共享进程的资源;
- 线程是处理器调度的基本单位,但进程不是;
生命周期
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞和终止。
- 新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start() 这个线程。
- 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
可以用下述图来进行理解线程的生命周期:
注:上述图来自http://www.runoob.com/wp-content/uploads/2014/01/java-thread.jpg。
在了解了线程和进程之后,我们再来简单的了解下单线程和多线程。
单线程
程序中只存在一个线程,实际上主方法就是一个主线程。
多线程
多线程是指在同一程序中有多个顺序流在执行。 简单的说就是在一个程序中有多个任务运行。
那么在什么情况下用多线程呢?
一般来说,程序中有两个以上的子系统需要并发执行的,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出高效的程序。
那么是不是使用很多线程就能提高效率呢?
不一定的。因为程序中上下文的切换开销也很重要,如果创建了太多的线程,CPU
花费在上下文的切换的时间将多于执行程序的时间!这时是会降低程序执行效率的。
所以有效利用多线程的关键是理解程序是并发执行而不是串行执行的。
线程的创建
一般来说,我们在对线程进行创建的时候,一般是继承Thread 类或实现Runnable 接口。其实还有一种方式是实现 Callable接口,然后与Future 或线程池结合使用, 类似于Runnable接口,但是就功能上来说更为强大一些,也就是被执行之后,可以拿到返回值。
这里我们分别一个例子使用继承Thread 类、实现Runnable 接口和实现Callable接口与Future结合来进行创建线程。
代码示例:
注:线程启动的方法是start而不是run。因为使用start方法整个线程处于就绪状态,等待虚拟机来进行调度。而使用run,也就是当作了一个普通的方法进行启动,这样虚拟机不会进行线程调度,虚拟机会执行这个方法直到结束后自动退出。
代码示例:
public class Test {
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
threadTest.start();
RunalbeTest runalbeTest=new RunalbeTest();
Thread thread=new Thread(runalbeTest);
thread.start();
CallableTest callableTest=new CallableTest();
FutureTask<Integer> ft = new FutureTask<Integer>(callableTest);
Thread thread2=new Thread(ft);
thread2.start();
try {
System.out.println("返回值:"+ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("这是一个Thread的线程!");
}
}
class RunalbeTest implements Runnable{
@Override
public void run() {
System.out.println("这是一个Runnable的线程!");
}
}
class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("这是一个Callable的线程!");
return 2;
}
}
运行结果:
这是一个Thread的线程!
这是一个Runnable的线程!
这是一个Callable的线程!
返回值:2
通过上述示例代码中,我们发现使用继承 Thread 类的方式创建线程时,编写最为简单。而使用Runnable、Callable 接口的方式创建线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用start方法来运行线程代码。顺便说下,其实Thread类实际上也是实现了Runnable接口的一个类。
但是在这里,我推荐大家创建单线程的时候使用继承 Thread 类方式创建,多线线程的时候使用Runnable、Callable 接口的方式来创建创建线程。
至于为什么呢?在下面中的描述已给出理由。
- 继承 Thread 类创建的线程,可以直接使用Thread类中的方法,比如休眠直接就可以使用sleep方法,而不必在前面加个Thread;获取当前线程Id,只需调用getId就行,而不必使用Thread.currentThread().getId() 这么一长串的代码。但是使用Thread 类创建的线程,也有其局限性。比如资源不能共享,无法放入线程池中等等。
- 使用Runnable、Callable 接口的方式创建的线程,可以实现资源共享,增强代码的复用性,并且可以避免单继承的局限性,可以和线程池完美结合。但是也有不好的,就是写起来不太方便,使用其中的方法不够简介。
总的来说就是,单线程建议用继承 Thread 类创建,多线程建议- 使用Runnable、Callable 接口的方式创建。
线程的一些常用方法
yield
使用yield方法表示暂停当前正在执行的线程对象,并执行其他线程。
代码示例:
public class YieldTest {
public static void main(String[] args) {
Test1 t1 = new Test1("张三");
Test1 t2 = new Test1("李四");
new Thread(t1).start();
new Thread(t2).start();
}
}
class Test1 implements Runnable {
private String name;
public Test1(String name) {
this.name=name;
}
@Override
public void run() {
System.out.println(this.name + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println(""+this.name + "-----" + i);
// 当为3的时候,让出资源
if (i == 3) {
Thread.yield();
}
}
System.out.println(this.name + " 线程运行结束!");
}
}
执行结果一:
张三 线程运行开始!
张三-----1
张三-----2
张三-----3
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----4
张三-----5
张三 线程运行结束!
李四-----4
李四-----5
李四 线程运行结束!
执行结果二:
张三 线程运行开始!
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----1
张三-----2
张三-----3
李四-----4
李四-----5
李四 线程运行结束!
张三-----4
张三-----5
张三 线程运行结束!
上述中的例子我们可以看到,启动两个线程之后,哪个线程先执行到3,就会让出资源,让另一个线程执行。
在这里顺便说下,yield和sleep的区别。
- yield: yield只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
- sleep:sleep使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
join
使用join方法指等待某个线程终止。也就是说当子线程调用了join方法之后,后面的代码只有等待该线程执行完毕之后才会执行。
如果不好理解,这里依旧使用一段代码来进行说明。
这里我们创建两个线程,并使用main方法执行。顺便提一下,其实main方法也是个线程。如果直接执行的话,可能main方法执行完毕了,子线程还没执行完毕,这里我们就让子线程使用join方法使main方法最后执行。
代码示例:
public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "主线程开始运行!");
Test2 t1=new Test2("A");
Test2 t2=new Test2("B");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
class Test2 extends Thread{
public Test2(String name) {
super(name);
}
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}
执行结果:
main主线程开始运行!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
main主线程运行结束!
上述示例中的结果显然符合我们的预期。
priority
使用setPriority表示设置线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式
- static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。
- static int MIN_PRIORITY 线程可以具有的最低优先级,取值为1。
- static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。
但是设置优先级并不能保证线程一定先执行。我们可以通过一下代码来验证。
代码示例:
public class PriorityTest {
public static void main(String[] args) {
Test3 t1 = new Test3("张三");
Test3 t2 = new Test3("李四");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
class Test3 extends Thread {
public Test3(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}
执行结果一:
李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程张三运行 : 2
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 3
子线程李四运行 : 5
李四 线程运行结束!
子线程张三运行 : 4
子线程张三运行 : 5
张三 线程运行结束!
执行结果二:
张三 线程运行开始!
子线程张三运行 : 1
李四 线程运行开始!
子线程李四运行 : 1
子线程张三运行 : 2
子线程张三运行 : 3
子线程李四运行 : 2
子线程张三运行 : 4
子线程李四运行 : 3
子线程张三运行 : 5
子线程李四运行 : 4
张三 线程运行结束!
子线程李四运行 : 5
李四 线程运行结束!
执行结果三:
李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 2
子线程张三运行 : 3
子线程张三运行 : 4
子线程李四运行 : 5
子线程张三运行 : 5
李四 线程运行结束!
张三 线程运行结束!
线程中一些常用的方法
线程中还有许多方法,但是这里并不会全部细说。只简单的列举了几个方法使用。更多的方法使用可以查看相关的API文档。这里我也顺便总结了一些关于这些方法的描述。
- sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);不会释放对象锁。
- join:指等待t线程终止。
- yield:暂停当前正在执行的线程对象,并执行其他线程。
- setPriority:设置一个线程的优先级。
- interrupt:一个线程是否为守护线程。
- wait:强迫一个线程等待。它是Object的方法,也常常和sleep作为比较。需要注意的是wait会释放对象锁,让其它的线程可以访问;使用wait必须要进行异常捕获,并且要对当前所调用,即必须采用synchronized中的对象。
- isAlive: 判断一个线程是否存活。
- activeCount: 程序中活跃的线程数。
- enumerate: 枚举程序中的线程。
- currentThread: 得到当前线程。
- setDaemon: 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)。
- setName: 为线程设置一个名称。
- notify(): 通知一个线程继续运行。它也是Object的一个方法,经常和wait方法一起使用。
结语
其实这篇文章很久之前都已经打好草稿了,但是由于各种原因,只到今天才写完。虽然也只是简单的介绍了一下多线程的相关知识,也只能算个入门级的教程吧。不过写完之后,感觉自己又重新复习了一遍多线程,对多线程的理解又加深了一些。
话已尽此,不在多说。
原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!
参考:https://blog.csdn.net/evankaka/article/details/44153709#t1
java高并发之线程池
Java高并发之线程池详解
线程池优势
在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理.
例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升.
另外一个好处是可以设定池化对象的上限, 例如预防创建线程数量过多导致系统崩溃的场景.
jdk中的线程池
下文主要从以下几个角度讲解:
- 创建线程池
- 提交任务
- 潜在宕机风险
- 线程池大小配置
- 自定义阻塞队列BlockingQueue
- 回调接口
- 自定义拒绝策略
- 自定义ThreadFactory
- 关闭线程池
创建线程池
我们可以通过自定义ThreadPoolExecutor或者jdk内置的Executors来创建一系列的线程池
- newFixedThreadPool: 创建固定线程数量的线程池
- newSingleThreadExecutor: 创建单一线程的池
- newCachedThreadPool: 创建线程数量自动扩容, 自动销毁的线程池
- newScheduledThreadPool: 创建支持计划任务的线程池
上述几种都是通过new ThreadPoolExecutor()来实现的, 构造函数源码如下:
1 /**
2 * @param corePoolSize 池内核心线程数量, 超出数量的线程会进入阻塞队列
3 * @param maximumPoolSize 最大可创建线程数量
4 * @param keepAliveTime 线程存活时间
5 * @param unit 存活时间的单位
6 * @param workQueue 线程溢出后的阻塞队列
7 */
8 public ThreadPoolExecutor(int corePoolSize,
9 int maximumPoolSize,
10 long keepAliveTime,
11 TimeUnit unit,
12 BlockingQueue<Runnable> workQueue) {
13 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
14 }
15
16 public static ExecutorService newFixedThreadPool(int nThreads) {
17 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
18 }
19
20 public static ExecutorService newSingleThreadExecutor() {
21 return new Executors.FinalizableDelegatedExecutorService
22 (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
23 }
24
25 public static ExecutorService newCachedThreadPool() {
26 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
27 }
28
29 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
30 return new ScheduledThreadPoolExecutor(corePoolSize);
31 }
32
33 public ScheduledThreadPoolExecutor(int corePoolSize) {
34 super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue());
35 }
提交任务
直接调用executorService.execute(runnable)或者submit(runnable)即可,
execute和submit的区别在于submit会返回Future来获取任何执行的结果.
我们看下newScheduledThreadPool的使用示例.
1 public class SchedulePoolDemo {
2
3 public static void main(String[] args){
4 ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
5 // 如果前面的任务没有完成, 调度也不会启动
6 service.scheduleAtFixedRate(new Runnable() {
7 @Override
8 public void run() {
9 try {
10 Thread.sleep(2000);
11 // 每两秒打印一次.
12 System.out.println(System.currentTimeMillis()/1000);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 }
17 }, 0, 2, TimeUnit.SECONDS);
18 }
19 }
潜在宕机风险
使用Executors来创建要注意潜在宕机风险.其返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM.
- CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.
综上所述, 在可能有大量请求的线程池场景中, 更推荐自定义ThreadPoolExecutor来创建线程池, 具体构造函数配置见下文.
线程池大小配置
一般根据任务类型进行区分, 假设CPU为N核
- CPU密集型任务需要减少线程数量, 降低线程之间切换造成的开销, 可配置线程池大小为N + 1.
- IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2.
- 混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置.
自定义阻塞队列BlockingQueue
主要存放等待执行的线程, ThreadPoolExecutor中支持自定义该队列来实现不同的排队队列.
- ArrayBlockingQueue:先进先出队列,创建时指定大小, 有界;
- LinkedBlockingQueue:使用链表实现的先进先出队列,默认大小为Integer.MAX_VALUE;
- SynchronousQueue:不保存提交的任务, 数据也不会缓存到队列中, 用于生产者和消费者互等对方, 一起离开.
- PriorityBlockingQueue: 支持优先级的队列
回调接口
线程池提供了一些回调方法, 具体使用如下所示.
1 ExecutorService service = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>()) {
2
3 @Override
4 protected void beforeExecute(Thread t, Runnable r) {
5 System.out.println("准备执行任务: " + r.toString());
6 }
7
8 @Override
9 protected void afterExecute(Runnable r, Throwable t) {
10 System.out.println("结束任务: " + r.toString());
11 }
12
13 @Override
14 protected void terminated() {
15 System.out.println("线程池退出");
16 }
17 };
可以在回调接口中, 对线程池的状态进行监控, 例如任务执行的最长时间, 平均时间, 最短时间等等, 还有一些其他的属性如下:
- taskCount:线程池需要执行的任务数量.
- completedTaskCount:线程池在运行过程中已完成的任务数量.小于或等于taskCount.
- largestPoolSize:线程池曾经创建过的最大线程数量.通过这个数据可以知道线程池是否满过.如等于线程池的最大大小,则表示线程池曾经满了.
- getPoolSize:线程池的线程数量.如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减.
- getActiveCount:获取活动的线程数.
自定义拒绝策略
线程池满负荷运转后, 因为时间空间的问题, 可能需要拒绝掉部分任务的执行.
jdk提供了RejectedExecutionHandler接口, 并内置了几种线程拒绝策略
- AbortPolicy: 直接拒绝策略, 抛出异常.
- CallerRunsPolicy: 调用者自己执行任务策略.
- DiscardOldestPolicy: 舍弃最老的未执行任务策略.
使用方式也很简单, 直接传参给ThreadPool
1 ExecutorService service = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
2 new SynchronousQueue<Runnable>(),
3 Executors.defaultThreadFactory(),
4 new RejectedExecutionHandler() {
5 @Override
6 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
7 System.out.println("reject task: " + r.toString());
8 }
9 });
自定义ThreadFactory
线程工厂用于创建池里的线程. 例如在工厂中都给线程setDaemon(true), 这样程序退出的时候, 线程自动退出.
或者统一指定线程优先级, 设置名称等等.
1 class NamedThreadFactory implements ThreadFactory {
2 private static final AtomicInteger threadIndex = new AtomicInteger(0);
3 private final String baseName;
4 private final boolean daemon;
5
6 public NamedThreadFactory(String baseName) {
7 this(baseName, true);
8 }
9
10 public NamedThreadFactory(String baseName, boolean daemon) {
11 this.baseName = baseName;
12 this.daemon = daemon;
13 }
14
15 public Thread newThread(Runnable runnable) {
16 Thread thread = new Thread(runnable, this.baseName + "-" + threadIndex.getAndIncrement());
17 thread.setDaemon(this.daemon);
18 return thread;
19 }
20 }
关闭线程池
跟直接new Thread不一样, 局部变量的线程池, 需要手动关闭, 不然会导致线程泄漏问题.
默认提供两种方式关闭线程池.
- shutdown: 等所有任务, 包括阻塞队列中的执行完, 才会终止, 但是不会接受新任务.
- shutdownNow: 立即终止线程池, 打断正在执行的任务, 清空队列.
原文链接:https://www.cnblogs.com/xdecode/p/9119794.html
Java并发性和多线程介绍目录
- Java并发性和多线程介绍
- 多线程的优点
- 多线程的代价
- 并发编程模型
- 如何创建并运行java线程
- 竞态条件与临界区
- 线程安全与共享资源
- 线程安全及不可变性
- Java内存模型
- JAVA同步块
- 线程通信
- Java ThreadLocal
- Thread Signaling (未翻译)
- 死锁
- 避免死锁
- 饥饿和公平
- 嵌套管程锁死
- Slipped Conditions
- Java中的锁
- Java中的读/写锁
- 重入锁死
- 信号量
- 阻塞队列
- 线程池
- CAS
- 剖析同步器
- 无阻塞算法
- 阿姆达尔定律
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java并发性和多线程介绍目录