多线程
一、创建线程的四种方式
创建线程的几种方式:
1、通过继承Thread类来创建一个线程
2、通过实现Runnable接口来创建Thread线程,
3、与方法2类似,通过实现Callable接口来创建Thread线程
4、使用Executor框架来创建线程池
编写多线程程序是为了实现多任务的并发执行,从而能够更好地与用户交互。一般有四种方法,Thread,Runnable,Callable,使用Executor框架来创建线程池。
Runnable和Callable的区别是,
(1)Callable规定的方法是call(),Runnable规定的方法是run()
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
四种方法如下:
①、通过继承Thread类来创建一个线程:
步骤1:定义一个继承Thread类的子类:
class MyThead extends Thread{
public void run(){
for(int i = 1; i<=50; i++){
System.out.println("MyRunnable :"+ i);
}
}
}
步骤2:构造子类的一个对象:
MyThread oneThread = new MyThread();
步骤3:启动线程:
oneThread.start();
至此,一个线程就创建完成了。
注释:这种创建线程的方法不够好,java只能单继承,现在继承了Thread类,就不能再继承其他类
②、通过实现Runnable接口来创建Thread线程:
步骤1:创建实现Runnable接口的类:
class MyRunnable implements Runnable
{
public void run()
{
//do something here
}
}
步骤2:创建一个类对象:
Runnable mr = new MyRunnable();
步骤3:创建一个Thread对象:
Thread t1 = new Thread(mr);
步骤4:启动线程:
t1.start();
至此,一个线程就创建完成了。
注释:线程的执行流程: 当执行代码t1.start();时,就会执行MyRunnable对象中的void run();方法,该方法执行完成后,线程就消亡了。
③、与方法1类似,通过实现Callable接口来创建Thread线程
其中Callable接口(也只有一个方法)定义如下:
public interface Callable<V>
{
V call() throws Exception;
}
步骤1:创建实现Callable接口的类MyCallable(略);
class MyCallable implements Callable
{
public void call()
{
//do something here
}
}
不加泛型:
Callable mc = new MyCallable();
FutureTask oneTask = new FutureTask(mc);
Thread oneThread = new Thread(oneTask);
oneThread.start();
步骤2:创建一个类对象:
Callable<Integer> mc = new MyCallable<Integer>();
步骤3:由Callable创建一个FutureTask对象:
FutureTask<Integer> oneTask = new FutureTask<Integer>(mc);
注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
步骤4:由FutureTask创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
至此,一个线程就创建完成了。
④、使用Executor框架来创建线程池
为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效地进行线程控制。它们都在java.util.concurrent包中,是JDK并发包的核心。其中有一个比较重要的类:Executors,它扮演着线程工厂的角色,我们通过Executors可以创建特定功能的线程池。
Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
Executors创建线程池方法:
1、newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂时缓存在一个任务队列中,等待有空闲的线程去执行。
public static ExecutorService newFixedThreadPool(int threads){
return new ThreadPoolExecutor(threads, threads,
OL,TimeUnit.MILLISECONDS,
new LinkdedBlockingQueue<Runnable>());
}
2、newSingleThreadExecutor()方法,创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中
public static ExecutorService newSingleThreadExecutor(int threads){
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor (1, 1,
OL,TimeUnit.MILLISECONDS,
new LinkdedBlockingQueue<Runnable>()));
}
3、newCachedThreadPool()方法,返回一个可以根据实际情况调整线程个数的线程池,不限制最大线程数量。只要有一个任务来了,就创建一个线程去执行任务。若无任务则不创建线程。并且每一个空闲线程会在60秒后自动回收。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4、newScheduledThreadPool()方法,该方法返回一个ScheduledExecutorService对象。但该线程池可以指定线程的数量,其中的每一个线程可以实现定时器的作用。
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE,0, MANOSECONDS,
new DelayedWorkQueue());
}
上面这些线程池底层都调用了ThreadPoolExecutor,不同类的线程池传递给ThreadPoolExecutor的参数不同
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue){
this(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,
Executors.defaultThreadFactory(),defaultHandler);
}
二、线程的五种状态:
1、初始状态(new)(新生状态):Thread T = new Thread();
2、就绪状态(Runnable):T.start();
3、运行状态(Running):T线程由*就绪状态*(仅这一种)获取到CPU为其分配的时间片,此时才真正执行线程体的代码块
4、死亡状态(Dead):run(),main()结束
5、阻塞状态(Blocked):调用sleep()、wait()或同步锁定时,线程进入阻塞状态,阻塞结束后重新进入*就绪状态*,等待CPU调度
进入就绪状态的四种原因:
1、T.start();
2、阻塞状态结束进入就绪状态
3、T.yield(); 自己中断运行状态进入就绪状态,让出CPU时间片等待重新调度,仍然有可能立即被调度,公平竞争,主要用来避免一个线程占用太多CPU时间片
4、JVM将本机线程切换到其他线程
进入阻塞状态的四种原因:
1、T.sleep(); 运行状态变成阻塞,自身占有资源不用,进行休眠,每个对象都有一个锁对象,sleep时不释放锁对象 静态方法,直接写在方法体中
2、T.wait(); 运行状态进入等待队列,自身不占资源,等待状态 静态方法,直接写在方法体中
3、同步锁定时: 当需要锁对象而没有拿到时
4、T.join(); 插队,直接进入运行状态,执行完之后再执行其他线程,其他线程会因此阻塞,该方法写在哪个线程中,哪个线程被阻塞
进入死亡状态:
1、线程正常结束运行
2、T.stop()/destroy(); 强行停止(不推荐使用,本身被JDK废弃了)
3、自己控制线程结束: 比如加一个boolean类型的变量,当该变量为false时,终止线程的运行
三、线程的同步(并发):
处理多线程问题时,多个线程访问同一个对象,并且某些线程还会修改该对象,这时就会用到“线程同步(并发)”。
由于同一个进程的多个线程共享同一资源,因此会产生访问冲突问题,为了保证访问信息数据资源时的正确性,需要加入“锁机制(synchronized)”。
线程同步其实就是一种等待机制+锁机制,多个同时访问此对象的线程进入这个对象的等待池形成“队列”并加上“锁机制”,获得锁的线程使用该对象资源,用完后释放锁,下一个竞争到该锁的线程拥有使用该对象资源的权利
synchronized关键字的两种用法:
1、synchronized方法(同步方法):将一个方法声明为synchronized,对一个方法加锁,如果方法太大,会大大影响效率
synchronized 返回值类型 方法名称(形参列表0){//对当前对象加锁
//代码(原子操作)
}
2、synchronized块(同步代码块):对代码块(原子操作)这个临界资源对象加锁。每个对象都有一个互斥锁标记用来分配给线程 多用6
synchronized(临界资源对象,比如account){//对临界资源对象加锁,一般该对象就是要修改的对象
//代码(原子操作)
}
Lock接口:
比synchronized有更多实用型方法,功能更强大,性能更优越,
void lock(); //获取锁
boolean tryLock(); //尝试获取锁
void unLock();//释放锁
实现类:
ReentrantLock(可重入锁):如果一个线程试图获取一个已经由他自己持有的锁时,那么这个请求会立刻成功,并将这个锁的计数+1,当线程退出同步代码块时,计数-1,知道为0为止,这时锁会彻底释放,没有可重入锁的话,就会造成死锁,
ReentrantLockReadWriteLock(读写锁)