多项活动同时进行的思想在Java中称为并发,而将并发完成的每一件事称为线程。每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程
多线程
进程与线程: Windows系统是多任务操作系统,它以进程为单位。每个独立执行的程序都称为进程,一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程。
在单线程中,程序代码按照调用顺序依次往下执行;在多线程中,一个进程可以同时完成多段代码的操作。
实现线程的两种方式:
(1)继承java.lang.Thread类
(2)实现java.lang.Runnable接口
继承Thread类
Thread类是java.lang包中的一个类,从这个类中实例化的对象代表线程。
public class TestThread extends Thread {//继承Thread类
private int count=10;
public void run(){//重写run()方法
while(true)
{
System.out.println(count--);
if(count==0)
{
break;
}
}
}
public static void main(String[] args) {
TestThread T=new TestThread();//实例化对象
T.start();//启动该线程
}
}
在主方法没有调用Thread类中的start()方法之前,Thread对象只是一个实例,而不是一个真正的线程
实现Runnable接口
当需要继承其他类(非Thread类)时,而且还要使当前类实现多线程,这时就可以通过Runnable接口来实现其多线程功能。(实质上Thread类实现了Runnable接口)
public class TestRunnable implements Runnable{//实现Runnable接口
private int count=10;
public void run(){//重写run()方法
while(true)
{
System.out.println(count--);
if(count==0)
{
break;
}
}
}
public static void main(String[] args) {
TestRunnable T=new TestRunnable();//实例化对象
Thread U=new Thread(T);
U.start();
}
}
创建TestRunnable类的实例后,将该实例作为Thread类构造方法的参数来调用start()方法启动线程
线程的生命周期
线程具有生命周期,其中包含七种状态。
(1)出生状态: 当使用线程实例调用start()方法之前
(2)就绪状态: 当使用线程实例调用start()方法之后 (又称可执行状态)
(3)运行状态: 当线程得到系统资源之后
一旦线程进入就绪状态(可执行状态),就会在就绪与运行状态下转换,同时也可能进入等待、休眠、阻塞、死亡状态
(4)等待状态: 当线程调用Thread类中的wait()方法时便进入等待状态,必须调用Thread类中的notify()方法才能被唤醒,而notifyAll()方法则是将所有处于等待状态的线程唤醒
(5)休眠状态: 当线程调用Thread类中的sleep()方法时便进入休眠状态
(6)阻塞状态: 当线程在运行状态下发出输入/输出请求,该线程将进入阻塞状;在其等待输入/输出结束时线程将进入就绪状态 (对于阻塞的线程来说,即使系统资源空闲,线程依旧不能回到运行状态)
(7)死亡状态: 当线程的run()方法执行完毕,线程进入死亡状态
虽然多线程看起来像同时执行,但实际上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生是同时进行的假象。 (在Windows操作系统中,系统会为每个线程分配一小段CPU时间片,一旦CPU时间片结束就会将当前线程换为下一个线程,即使该线程没有结束)
线程的操作方法
操作线程有很多方法,这些方法可以使线程从某一种状态过渡到另一种状态
线程的休眠: 调用sleep()方法
Thread.sleep(1000)//使线程休眠1000ms(1s)
线程的中断: 以往使用stop()方法,现今提倡在run()方法中使用无限循环,用布尔型标记控制循环的停止;若线程是因为使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类中的interrupt()方法使线程离开run()方法,同时结束线程。
public class TestInterrupt {
public static void main(String[] args) {
Thread T;
T=new Thread(new Runnable() {
@Override
public void run() {
while(true)
{
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
System.out.println("The Thread has been interrupted.");
break;
}
}
}
});
T.start();
T.interrupt();
}
}
在本实例中,由于调用了interrupted()方法,所以抛出了InterruptedException异常,从而被捕获,通过break结束循环
线程的礼让: Thread类中的yield()方法,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程。(对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行)
线程的优先级
每个线程都有各自的优先级来表明在程序中的重要性,当很多线程处于就绪状态,系统会根据优先级来决定首先使用哪个线程进入运行状态,通常用Thread类中的setPriority()方法来设置常数1~10的优先级。
线程的同步
Java提供了线程同步的机制来解决多个线程抢占资源,防止资源访问冲突,同步机制使用synchronized关键字 。
多个线程同时处理单个数据时可能会出现异常:
public class TestSynchronization implements Runnable{
int n=10;
@Override
public void run() {
while(true){
if(n>0)
{
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(n--);
}
}
}
public static void main(String[] args) {
TestSynchronization T=new TestSynchronization();
Thread A=new Thread(T);
Thread B=new Thread(T);
Thread C=new Thread(T);
Thread D=new Thread(T);
A.start();
B.start();
C.start();
D.start();
}
}
该实例本不应该输出负数,但由于多个线程同时对一个变量进行递减操作,当n自减到0时,有的线程处于休眠状态中(判断语句已经过了),所以休眠完之后继续执行后面的输出语句,导致输出负数,这是需要使用Java的同步机制解决该问题。
用synchronized关键字将对该数据操作的代码设置在同步块中:
public class TestSynchronization implements Runnable{
int n=10;
@Override
public void run() {
while(true){
synchronized (""){
if(n>0)
{
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(n--);
}
}
}
}
public static void main(String[] args) {
TestSynchronization T=new TestSynchronization();
Thread A=new Thread(T);
Thread B=new Thread(T);
Thread C=new Thread(T);
Thread D=new Thread(T);
A.start();
B.start();
C.start();
D.start();
}
}
同步机制使得在同步块中的代码被当前线程执行完后才能被其他线程执行,有效的防止资源冲突。
同步方法: 同步方法就是在方法前面修饰synchronized关键字的方法,当某个对象调用了同步方法中时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行*(必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错)*
synchronized void f(){}
将共享资源的操作放置在同步方法中,运行结果与使用同步块的结果一致