线程包:java.util.concurrent(jdk1.5以后)
线程池
线程池:它的作用就是限制系统中执行线程的数量。
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
比较重要的几个类:
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
实例
1:newSingleThreadExecutor
MyThread.java
publicclassMyThread extends Thread {
@Override
publicvoid run() {
System.out.println(Thread.currentThread().getName() + “正在执行。。。”);
}
}
TestSingleThreadExecutor.java
publicclassTestSingleThreadExecutor {
publicstaticvoid main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors. newSingleThreadExecutor();
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
输出结果
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
2 newFixedThreadPool
TestFixedThreadPool.Java
publicclass TestFixedThreadPool {
publicstaticvoid main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
输出结果
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-1正在执行。。。
3 newCachedThreadPool
TestCachedThreadPool.java
publicclass TestCachedThreadPool {
publicstaticvoid main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newCachedThreadPool();
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
输出结果:
pool-1-thread-2正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-5正在执行。。。
4 newScheduledThreadPool
TestScheduledThreadPoolExecutor.java
publicclass TestScheduledThreadPoolExecutor {
publicstaticvoid main(String[] args) {
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常
@Override
publicvoid run() {
//throw new RuntimeException();
System.out.println(“================”);
}
}, 1000, 5000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的
@Override
publicvoid run() {
System.out.println(System.nanoTime());
}
}, 1000, 2000, TimeUnit.MILLISECONDS);
}
}
输出结果
================
8384644549516
8386643829034
8388643830710
================
8390643851383
8392643879319
8400643939383
线程实现
一:继承Thread类
二:实现Runnable接口
由于在Java中只允许单继承,所以某类若继承了Thread类,则必定会受到单继承局限性的影响,不能再继承其它类。为摆脱这种影响,可以通过实现Runnable接口的形式实现多线程。
Runnable接口中只定义了一个抽象方法:public void run();
与继承Thread类相同,通过实现Runnable接口的方式实现多线程需要重写run()方法,启动多线程要使用start()方法来实现。继承了Thread类可以直接完成启动,但实现Runnable接口的线程类该如何启动呢?此时仍需要通过Thread类中提供的public Thread(Runnable target)构造方法,实现线程的启动。
package com.process;
public class ThreadDemo2 implements Runnable {
private String name;
public ThreadDemo2(String name) {
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=1;i<=5;i++){
System.out.println(name+"运行,i="+i);
}
}
public static void main(String[] args) {
new Thread(new ThreadDemo2("线程E")).start();
new Thread(new ThreadDemo2("线程G")).start();
}
}
启动多线程必须依靠Thread类实现。(new Thread(new ThreadDemo2(“线程E”)).start())通过Thread类中提供的public Thread(Runnable target)构造方法,实现线程的启动。
三:Thread类与RUnnable接口的区别
在多线程的实现方式中,Thread与Runnable接口在使用上是有区别的,如继承了Thread的类不适合多个线程共享资源,而实现了runnable接口可以方便地实现资源共享。
package com.process;
public class ThreadDemo4 extends Thread {
private int ticket=5;
private String name;
public ThreadDemo4(String name) {
this.name = name;
}
public void run(){
for(int i=1;i<=5;i++){
if(ticket>0){
System.out.println(name+”卖票:ticket=”+ticket–);
}
}
}
public static void main(String[] args) {
ThreadDemo4 d1=new ThreadDemo4(“线程A”);
ThreadDemo4 d2=new ThreadDemo4(“线程B”);
ThreadDemo4 d3=new ThreadDemo4(“线程C”);
d1.start();d2.start();d3.start();
}
}
通过Thread类实现多线程,程序中启动了三个线程,却分别卖出了各自的5张票,并没有达到资源共享的目的。因为创建了三个线程对象。就等于创建了三个资源。每个线程对象都有5张票要卖出,且独立地处理各自的资源,所以在售票系统中多个线程应处理同一资源即线程上应该运行相同的程序代码。通过runnable接口可以解决这一问题。
package com.process;
public class ThreadDemo5 implements Runnable{
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <=5; i++) {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票:ticket="+ticket--);
}
}
}
public static void main(String[] args) {
ThreadDemo5 d=new ThreadDemo5();
new Thread(d).start();
new Thread(d).start();
new Thread(d).start();
}
}
实现runnable接口启动了三个线程,共卖出了5张票。即ticket的属性是被所有线程对象共享。重点是thread启用线程是针对runnnable同一个run()方法。而直接启thread线程却要实例化Thread。
注意:runnable启动线程依旧是通过thread实例来启动的。也就说明启动线程,不管是thread类还是runnable都必须依赖Thread。
使用Ruannable优势:
1.适合多个具有相同程序代码的线程处理统一资源。
2.可以避免java的单继承特征带来的局限。
3.代码能够被多个线程共享且与数据独立存在,从而增强了程序的健壮性。
四:线程的操作方法
thread类长用方法:
public static Thread currentThread返回当前线程
public final String getName() 返回线程名称
public final void setpoipority(int priority)设置线程优先级
public void start() 开始执行线程
public static void sleep(long m) 使目前的线程休眠
public final void yield() 暂停目前线程,运行其他线程
public void run() 执行线程
五:同步与死锁
线程安全题
package com.process;
public class ThreadDemo5 implements Runnable{
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <=5; i++) {
if(ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票:ticket="+ticket--);
}
}
}
public static void main(String[] args) {
ThreadDemo5 d=new ThreadDemo5();
new Thread(d).start();
new Thread(d).start();
new Thread(d).start();
}
}
在加入延迟后,运行出现了负数。原因在于每卖一张票需要两部:第一步是判断是否有票,第二部是进行卖票(ticket–)。当剩下最后一张票后,线程1执行了第一步,还未执行第二部时系统已经切换至线程2;线程2完成2步之后又切回了线程1。此时线程1不需要执行第一步,直接执行的是第二部,就出现了票数为负数的情况。因此需要引入同步来解决此问题。
(1)线程同步
同步:同一时间段只能运行一个线程,其他线程需要等待此线程完成够=够才可以继续执行。同步可以解决线程中资源共享的安全问题。
线程同步主要通过同步代码块和同步方法两种方式完成。
1:同步代码块
代码块是指使用了”{}”括起来的一段代码。代码块前添加了synchronized关键字即为同步代码块。
格式:
synchronized(同步对象){
需要同步的代码;
}
需要同步代码块时必须指定需要同步的对象。所谓同步对象:为对象加上资源锁,即一定时间范围内置允许一个线程使用当前的同步对象,其他线程不需等待当前线程使用结束后才能使用该对象。通常情况下将当前this对象设置为同步对象。
package com.process;
public class ThreadDemo5 implements Runnable{
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <=5; i++) {
/*synchronized(this){*/
if(ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票:ticket="+ticket--);
}
/*}*/
}
}
public static void main(String[] args) {
ThreadDemo5 d=new ThreadDemo5();
new Thread(d).start();
new Thread(d).start();
new Thread(d).start();
}
}
解开上面代码的注释会发现:对获取ticket值的修改进行了同步,就不会出现票为负数的情况。
2:同步方法
同步方法:使用synchronized关键字声明方法,即为方法上加上资源访问锁,同一时间值允许一个线程调用同步方法,其他线程必须等待当前线程调用结束后才能调用同步方法。
同步方法的格式:
synchronized 方法返回类型 方法名称(参数列表){
}
package com.process;
public class Threaddemo6 implements Runnable {
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i < ticket; i++) {
saleTicket();
}
}
public synchronized void saleTicket(){
if(ticket>0){
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread()+":賣票ticket="+ticket--);
}
}
public static void main(String[] args) {
Threaddemo6 d=new Threaddemo6();
new Thread(d).start();
new Thread(d).start();
new Thread(d).start();
}
}
死锁
死锁:线程彼此等待对方操作而造成程序的长时间停滞。
package com.process;
class Father{
public void say(){
System.out.println(“对孩子说:给我成绩单,我就给你玩具”);
}
public void get(){
System.out.println(“爸爸得到成绩单”);
}
}
class Child{
public void say(){
System.out.println(“对父亲说:给我玩具,我就给你成绩单”);
}
public void get(){
System.out.println(“孩子得到玩具”);
}
}
public class threadDemo7 implements Runnable {
private static Father f=new Father();
private static Child c=new Child(); private boolean flag=false;
@Override
public void run() {
// TODO Auto-generated method stub
if(flag){//如果flag为true,则对father加同步锁
synchronized(f){
f.say();
try {
Thread.sleep(3000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
synchronized(c){//对child加同步资源锁
f.get();
}
}
}else{
synchronized(c){
c.say();
try {
Thread.sleep(3000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
synchronized(f){//对father加同步资源锁
c.get();
}
}
}
}
public static void main(String[] args) {
threadDemo7 d1=new threadDemo7();
d1.flag=true;
threadDemo7 d2=new threadDemo7();
d2.flag=false;
new Thread(d1).start();
new Thread(d2).start();
}
}
线程生命周期
1.创建状态
程序中使用构造方法创建线程对象后,新线程即处于创建状态。线程此时已经具有相应的内存空间和其他资源,但不能运行
2.就绪状态
线程对象创建后调用start()方法启动线程,即进入就绪状态。就绪下的线程进入线程队列等待cpu调用。
3.运行状态
线程获得cpu服务即处于运行状态,此时会自动调用线程对象的run()方法。run()方法中定义了该线程具体的操作和实现功能。需要注意的是运行状态调用yield()方法后,将从运行状态返回至就绪状态。
4.阻塞状态
运行状态的线程调用sleep()、wait()等方法后将进入阻塞状态。线程阻塞条件解除后,线程再次转入就绪状态。
5.终止状态
当线程执行run()方法完毕后处于终止状态(死亡状态),处于终止状态下的线程不具有继续运行的能力。
原文链接:http://m.blog.youkuaiyun.com/article/details?id=48087123