一、线程概述
(1)进程与线程:当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并具有一定的独立功能,多线程则拓展了多进程的概念,使得同一个进程可以同时并发处理多个任务,线程是进程的执行单元,多个线程组成了进程
(2)并发和并行:并发是指在同一个cpu上,同一时刻只能有一条指令执行。并行是指在同一时刻,有多条指令在多个处理器上同时执行。
(3)特点:1、进程间不能共享内存,而线程之间共享内存非常容易。2、系统创建进程需要为该进程重新分配系统资源,但是创建线程的代价小很多,效率更高。3、java内置多线程功能支持。
二、线程的创建和启动
线程创建有两种方法
(1)继承Thread类创建线程类
1、定义Thread子类,重写run方法
2、创建实例,即创建了线程对象
3、用线程对象的start方法来启动线程
package java_test;
public class FirstThread extends Thread
{
private int i;
//重写run方法
public void run()
{
for(;i<100;i++)
{
//getName()返回当前线程名字
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)
{
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
new FirstThread().start();//启动了第一条线程
new FirstThread().start();//启动了第二条线程
}
}
}
}
(不要忘记主线程的存在!)
(2)实现Runnable接口创建线程类
1、定义Runnable接口的实现类,并重写该接口的run方法
2、创建实例,并以此实例作为Thread的target来创建Thread对象
package java_test;
public class SecondThread implements Runnable{
private int i;
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
SecondThread st=new SecondThread();
new Thread(st,"Thread-0").start();
new Thread(st,"Thread-1").start();
}
}
}
@Override
public void run() {
// TODO Auto-generated method stub
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
(以上是用runnable实现创建线程,与第一种方法相比,可以看到多条线程可以共享同一个target的实例属性,所以i是连续的!)
推荐使用第二种方法创建线程
二、线程的生命周期
五种状态:新建(new)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(dead)
三、控制线程
java提供了一些方法可以很好地控制线程 的执行。
(1)join()方法
package java_test;
public class ThirdThread {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
ThirdThreadTest ttt=new ThirdThreadTest();
Thread t=new Thread(ttt,"子线程");
t.start();
t.join();
}
}
}
}
class ThirdThreadTest implements Runnable
{
private int i=0;
@Override
public void run() {
// TODO Auto-generated method stub
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
上述程序中有两个线程,分别为主线程和子线程,在主线程中加入了join()方法使得主线程必须等待子线程结束后才能继续运行。join()方法可以设置等待时间join(long millis)毫秒
(2)后台线程
有一种线程,在后台运行,特征:如果前台线程都死亡,后台线程会自动死亡(垃圾回收机制就是典型的后台线程)
Thread类提供了一个isDaemon()方法,用于判断指定线程是否是后台线程。
package java_test;
public class DaemonThread extends Thread{
public void run()
{
for(int i=0;i<1000;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
DaemonThread t=new DaemonThread();
t.setDaemon(true);//设置为后台线程
t.start();
for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
上述程序中将子线程设置为后台线程,当主线程结束后,程序认为前台线程全部死亡,那么后台线程也随之死亡,所有子线程无法全部运行完就死亡了。
(3)线程睡眠 sleep 线程让步yield
sleep方法会将线程转入阻塞状态,而yield不会,它只是强制将线程转入就绪状态,完全可能再次立刻获得处理器的资源再次执行。
(4)改变线程优先级
Thread.currentThread.setPriority(number)
Thread.setPriority(Thread.MIN_PRIORITY)//更合适
四、线程的同步
程序中可能会有多条并发线程在修改同一个文件。
为了解决这个问题,java提出了各种方法来解决这个问题。
(1)同步代码块
synchronized(obj)
{
//此处的代码就是同步代码块
}
通常推荐使用可能被并发访问的共享资源充当同步监视器。
(2)同步方法
使用关键字synchronized来修饰方法
public synchronized void draw()
{
}
同步方法的监视器是this,任何时刻只能有一条线程获得对对象的锁定。
synchronized可以修饰方法、代码块、不能修饰构造器、属性等。
(3)释放同步监视器的锁定
线程会在如下几种情况下释放对同步监视器的锁定;
1、当前线程的同步方法、代码块执行结束
2、在同步代码块、同步方法中遇到break、return终止了代码块、方法的执行
3、出现了未处理的Error
4、执行了同步监视器对象的wait()方法
(4)、同步锁
通过显示定义同步锁对象来实现同步
private final ReentrantLock lock=new ReentrantLock();//新建锁的对象
lock.lock();//上锁
。。。。。
finally
{
lock.unlock()//解锁
}
(5)死锁
当两个线程互相等待对方释放同步监视器时就会发生死锁
五、线程通信
(1)
Object提供了wait()、notify()、notifyAll()三个方法来进行线程的协调运行(ps:属于object类,而不属于Thread类),这三个方法必须由同步监视器对象来调用。
对于使用synchronized修饰的同步方法,可以直接调用这三个方法。
对于synchronized修饰的代码块,必须使用括号中的对象来调用这三个方法。
1、wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程
可以选择wait()(一直等待)、wait(long miles)等待一段时间,wait()方法的当前线程会释放对该同步监视器的锁定
2、notify():唤醒在此同步监视器上等待的当线程,如果所有线程都在此同步监视器上等待,随机唤醒一个线程
3、notifyAll():唤醒在此同步监视器上等待的所有线程。
(2)
如果不使用synchronized关键字来保持同步,而是直接用lock对象来保证同步,java提供了一个Condition类
创建Condition实例,使用Lock.newCondition()方法
Conditon提供了三个方法:
1、await():类似于wait();
2、signal()类似notigy();
3、signalAll()类似notigyAll()方法
六、Callable和Future
Callable接口类似Runnable的增强版,提供了一个call()方法作为线程执行体,但call方法比run()方法功能更为强大
call()方法可以有返回值
call()可以声明抛出异常
Future接口代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口 可以作为Thread类的target
创建、并启动有返回值的线程步骤如下:
1、创建Callable接口的实现类,并实现call()方法,且该call方法有返回值
2、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象call()方法的返回值
3、使用FutureTask对象作为Thread对象的target创建、并启动新进程
4、调用FutureTask对象的方法来获得子线程执行结束后的返回值
package java_test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class RtnThread implements Callable<Integer>
{
public static void main(String[] args) throws InterruptedException, ExecutionException
{
// TODO Auto-generated method stub
RtnThread rt=new RtnThread();
FutureTask<Integer> task=new FutureTask<Integer>(rt);
new Thread(task,"有返回值的线程").start();
System.out.println(task.get());
}
@Override
public Integer call() throws Exception
{
// TODO Auto-generated method stub
int i=0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+"的循环变量i的值"+i);
}
return i;
}
}
说白了就是有个返回值,创建时用FutureTask来包装一下,再在Thread里启动
七、线程池
当程序中需要创建大量生存期很短暂的线程时,应该考虑使用线程池,线程池在系统启动时创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池启动一条线程来执行run方法,执行结束后,线程不死亡,而是返回线程池中成为空闲状态,等待下一个Runnable。
使用线程池来执行线程任务的步骤如下:
1、调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
2、创建Runnable、Callable实例,作为线程执行任务
3、调用ExecuterService对象的submit方法来提交Runnable实例或Callable实例
4、用shutdown方法来关闭线程池
package java_test;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建了一个大小为6的线程池
ExecutorService pool=Executors.newFixedThreadPool(6);
//向线程池中提交两个线程
pool.submit(new ThreadPoolTest());
pool.submit(new ThreadPoolTest());
//关闭线程池
pool.shutdown();
}
}
class ThreadPoolTest implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+"的i值为"+i);
}
}
}