Java线程和线程池详解
java线程java线程的五个状态NEW(尚未启动的线程处于此状态)RUNNABLE(在java虚拟机中执行的线程处于此状态)BLOCKED(被阻塞等待监视器的状态锁定的线程处于此状态)WAITING(正在等待另一个线程执行特定动作的线程处于此状态)TIMED WAITING(正在等待另一个线程执行动作达到指定等待时间的线程处于此状态)TERMINATED(已退出的线程处于此状态)[外链图片转存失
java线程
java线程的五个状态
- NEW(尚未启动的线程处于此状态)
- RUNNABLE(在java虚拟机中执行的线程处于此状态)
- BLOCKED(被阻塞等待监视器的状态锁定的线程处于此状态)
- WAITING(正在等待另一个线程执行特定动作的线程处于此状态)
- TIMED WAITING(正在等待另一个线程执行动作达到指定等待时间的线程处于此状态)
- TERMINATED(已退出的线程处于此状态)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Um59MBqE-1600167252629)(/Users/Arithmetic/Library/Application Support/typora-user-images/image-20200615220834204.png)]
package threads;
/**
* t线程和主线程交替执行
*/
public class AllState extends Thread{
@Override
public void run() {
for(int i = 0;i < 10;i++){
try {
// sleep() 使当前线程进入阻塞状态,在指定时间不会执行
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
Thread t = new AllState();
//观察状态
State state = t.getState();
// NEW
System.out.println(state);
t.start();
//观察状态
state = t.getState();
// RUNNABLE
// 此时t线程进入RUNNABLE状态
System.out.println(state);
while(state != State.TERMINATED){
// 活动的线程数
int num = Thread.activeCount();
System.out.println("活动的线程数" + " " + num);
try {
// 100ms监控一次
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此时t线程TIMED_WAITING或者RUNNABLE
state = t.getState();
// TIMED_WAITING或者RUNNABLE
System.out.println(state);
}
}
}
java 阻塞
-
sleep():
Thread.sleep(1000);
在指定时间内让当前执行的线程暂停执行一段时间,让其他线程有机会继续执行,但不会释放对象锁,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据,不推荐使用,sleep() 使当前线程进入阻塞状态,在指定时间不会执行。 -
wait():
对象的方法,会释放对象锁
wait()和notify()、notifyAll(),这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用也就是说,调用wait(),notify()和notifyAll()的任务在调用这些犯法前必须拥有对象锁
wait()和notify()、notifyAll()它们都是Object类的方法,而不是Thread类的方法。
当调用某一对象的wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获得锁标志,他们随时准备争夺锁的拥有权,当调用了某个对象的notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池
wait():调用该方法使持有该对象的线程把该对象的控制权交出去,然后处于等待状态
notify():调用该方法就会通知某个正在等待这个对象的控制权的线程可以继续运行
notifyAll():调用该方法就会通知所有等待这个对象控制权的线程继续运行 -
yield():
Thread类的静态方法,不会释放对象锁,不抛异常
yield() 方法和sleep() 方法类似,也不会释放对象锁,它是Thread类的静态方法,区别在于,它没有参数,即yield() 方法只是使当前线程让步,重新回到就绪状态,所以执行yield的线程,有可能在进入到就绪状态后马上又被执行,另外yield方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep方法不同 -
join():
Thread 类的对象实例的方法
Thread t1 = new Thread();t1.join();
join() 方法会使当前线程等待调用join()方法的县城结束后才能继续执行
java中创建线程的三种方式
-
继承Thread类实现多线程
-
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
-
创建Thread子类的实例,即创建了线程对象。
-
调用线程对象的start()方法来启动该线程。
package threads; /** * 一、继承Thread类创建线程类 * * (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 * * (2)创建Thread子类的实例,即创建了线程对象。 * * (3)调用线程对象的start()方法来启动该线程。 */ public class ExtendThread extends Thread{ @Override public void run() { //重写run()方法 } public static void main(String[] args) { new ExtendThread().start(); } }
-
-
重写Runnable()接口实现多线程
-
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
-
创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
-
调用线程对象的start()方法来启动该线程。
package threads; /** * 通过Runnable接口创建线程类 * (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 * (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 * * (3)调用线程对象的start()方法来启动该线程。 */ public class OverrideRunnable implements Runnable{ @Override public void run() { //重写run()方法 } public static void main(String[] args) { OverrideRunnable orr = new OverrideRunnable(); new Thread(orr).start(); } }
-
-
通过Callable和Future创建线程
-
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
-
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
-
使用FutureTask对象作为Thread对象的target创建并启动新线程。
-
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
package threads; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThread implements Callable { public static void main(String[] args) { CallableThread ct = new CallableThread(); FutureTask<Integer> ft = new FutureTask<>(ct); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Object call() throws Exception { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }
-
join()
join(多线程插队,谁join谁就是插队,只有执行完join的线程,才能继续执行其他线程)
必须先start再join
package threads;
/**
* 插队join
*/
public class BlockJoin extends Thread{
@Override
public void run() {
//重写run()方法
for(int i = 0; i < 100;i++){
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
// run()普通方法调用
// new ExtendThread().run();
Thread t = new BlockJoin();
t.start();
for(int i = 0;i < 100;i++){
if(i == 20){
t.join();//插队,main被阻塞了,只有t线程执行完了,才能开始执行main线程
}
System.out.println("main..." + i);
}
}
}
优先级
-
注:优先级低只是意味着获得调度的概率低,并不是绝对先调用优先级高后调用优先级低的线程
-
优先级的设定建议在start()调用前
-
* NORM_PRIORITY 5 默认 * MIN_PRIORITY 1 * MAX_PRIORITY 10 * 概率,不代表绝对的先后顺序
setDaemon()守护线程
虚拟机等待所有用户线程执行完毕,才会停止,但是不会等待守护线程
package threads;
/**
* 守护线程
* 虚拟机等待所有用户线程执行完毕,才会停止,但是不会等待守护线程
*
*/
public class Daemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread t = new Thread(god);
// 将用户线程设置为守护,虚拟机不用等待守护线程执行完成
t.setDaemon(true);
// new Thread(god).start();
new Thread(you).start();
}
}
class You implements Runnable{
@Override
public void run() {
for(int i = 0;i < 365 * 100; i++){
System.out.println("Happy Life");
}
System.out.println("ooooop");
}
}
class God implements Runnable{
@Override
public void run() {
for(;true;){
System.out.println("Bless You");
}
}
}
线程常用方法
-
isAlive()
-
setName()
-
getName()
-
currentThread()
package threads; public class ThreadInfo { public static void main(String[] args) { System.out.println(Thread.currentThread().isAlive()); MyInfo info = new MyInfo("代理角色"); Thread t = new Thread(info); t.setName("真实角色"); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.isAlive()); } } class MyInfo implements Runnable{ private String name; public MyInfo(String name){ this.name = name; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " " + name); } }
线程同步(synchronized)
-
线程不安全
- 数据有负数
- 数据有重复
package threads.synchronize; public class UnsafeThread { public static void main(String[] args) { UnsafeWeb12306 web12306 = new UnsafeWeb12306(); new Thread(web12306, "张三").start(); new Thread(web12306, "李四").start(); new Thread(web12306, "王五").start(); } } class UnsafeWeb12306 implements Runnable{ // 票数 private int tickerNum = 10; private boolean flag = true; @Override public void run() { while (flag){ BuyTicket(); } } public void BuyTicket(){ while (true){ if(tickerNum < 0){ flag = false; return; } // 模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->" + tickerNum--); } } }
线程池(ThreadPoolExecutor)
https://blog.csdn.net/ssjdoudou/article/details/105400156
为什么要有线程池
-
为每个请求创建一个新线程的开销很大;
-
为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。
-
除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。
-
线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。
使用线程池的风险
死锁
资源不足
线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。
并发错误
线程池和其它排队机制依靠使用 wait() 和 notify() 方法,这两个方法都难于使用。如果编码不正确,那么可能丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理。使用这些方法时,必须格外小心。而最好使用现有的、已经知道能工作的实现,例如 util.concurrent 包。
线程泄漏
各种类型的线程池中一个严重的风险是线程泄漏,当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
请求过载
仅仅是请求就压垮了服务器,这种情况是可能的。在这种情形下,我们可能不想将每个到来的请求都排队到我们的工作队列,因为排在队列中等待执行的任务可能会消耗太多的系统资源并引起资源缺乏。在这种情形下决定如何做取决于您自己;在某些情况下,您可以简单地抛弃请求,依靠更高级别的协议稍后重试请求,您也可以用一个指出服务器暂时很忙的响应来拒绝请求。
四种创建方式
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
构造方法参数说明
- corePoolSize
核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
- maximumPoolSize
线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
- keepAliveTime
非核心线程的闲置超时时间,超过这个时间就会被回收。
- unit
指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
- workQueue
线程池中的任务队列.
常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
- threadFactory
线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
拒绝策略
接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
拒绝策略 | 拒绝行为 |
---|---|
AbortPolicy | 抛出RejectedExecutionException |
DiscardPolicy | 什么也不做,直接忽略 |
DiscardOldestPolicy | 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置 |
CallerRunsPolicy | 直接由提交任务者执行这个任务 |
更多推荐
所有评论(0)