首先要了解操作系统中进程与线程的区别;
进程:是程序的一次动态执行过程,它对应了代码的加载、执行至执行完毕的完整过程,这个过程也是进程本身从产生、发展至消亡的过程,每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位);
线程:是比进程更小的单位,进程在其执行过程中,可以产生多个线程,形成多执行线索,即每个线程也有它自身的产生、存在和消亡过程,也是一个动态的概念,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位);
线程与进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指系统可以同时运行多个任务。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口。
一、继承Thread类
public class ThreadByExtends_1 {
public static void main(String[] args) {//主线程
SpeakHello sh = new SpeakHello();
SpeakNihao sn = new SpeakNihao();
sh.start();
sn.start();
for(int i = 1; i <= 5; i++) {
System.out.println("大家好"+i+" ");
}
}
}
class SpeakHello extends Thread{
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println("hello"+i+" ");
}
}
}
class SpeakNihao extends Thread{
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println("你好"+i+" ");
}
}
}
运行结果:
大家好1
hello1
你好1
hello2
大家好2
hello3
你好2
hello4
大家好3
hello5
你好3
你好4
你好5
大家好4
大家好5
第二次运行:
大家好1
你好1
hello1
你好2
大家好2
你好3
hello2
你好4
大家好3
你好5
大家好4
大家好5
hello3
hello4
hello5
当执行一个线程程序是,就自动产生一个线程,主方法正式在这个线程上运行的。当不在启动其他线程时,该程序就是单线程程序。主方法线程启动有Java虚拟机负责,程序员负责自己的线程。在main方法中,使线程执行需要调用Thread类中的start()方法,start()方法调用被覆盖的run()方法,如果不调用start()方法,线程永远不会启动,在主方法中没有调用start()方法之前,Thread对象只是一个实例,而不是线程;start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
二、实现Runnable接口
采用Runnable也是非常常见的一种,我们只需要重写run方法即可。
public class ThreadByRunnable {
public static void main(String[] args) {
Bank bank = new Bank();
bank.setMoney(300);
Thread threadOne = new Thread(bank);
threadOne.setName("One");
Thread threadTwo = new Thread(bank);
threadTwo.setName("Two");
threadOne.start();
threadTwo.start();
}
}
class Bank implements Runnable{
private int number = 0;
public void setMoney(int m) {
number = m;
}
@Override
public void run() {
while(true) {
String name = Thread.currentThread().getName();
if(name.equals("One")) {
if(number <= 160) {
System.out.println(name+"进入死亡状态");
return; //threadOne的run方法结束
}
number = number+10;
System.out.println("我是"+name+"现在number="+number);
}
if(Thread.currentThread().getName().equals("Two")) {
if(number <= 0) {
System.out.println(name+"进入死亡状态");
return;
}
number = number-100;
System.out.println("我是"+name+"现在number="+number);
}
try {
Thread.sleep(800);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
使用Runnable接口启动线程的步骤如下:
(1)建立Runnable对象。
(2)使用参数为Runnable对象的构成方法创建Thread实例。
(3)调用start()方法启动线程
三、继承Thread类和实现Runnable接口的比较
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享,适合多个相同的程序代码的线程去处理同一个资源,可以避免java中的单继承的限制,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立;还有就是线程池只能放入实现Runable,不能直接放入继承Thread的类。
四、线程的生命周期
线程具有的生命周期,包含7种状态,分别为出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。出生状态就是线程被创建时处于的状态,在用户使用线程实例调用start()方法之前线程都是处于出生状态;当用户调用start()方法后,线程处于就绪状态(可执行状态);当线程得到系统资源后就进入运行状态。一旦线程进入可执行状态,它就会在 就绪与运行状态下转换,同时也可能进入等待、休眠、阻塞或死亡状态。当处于运行状态下的线程调用Thread类中的wait()方法时,该线程便进入了等待状态,进入等待状态的线程必须调用Thread类中的notify()方法才能被唤醒;当线程调用Thread类中的sleep()方法时,则会进入休眠状态。如果一个线程在运行状态下发出输入/输出的请求,该线程将会进入阻塞状态,在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程;来说,即使系统资源空闲,线程依旧不能回到运行状态。当线程的run()方法执行完毕时,线程进入死亡状态。
虽然多线程看起来像同时执行,但事实上在同一时间上只有一个线程被执行,只是线程之间切换太快,所以才会使人产生线程时同时进行的假象。在Windows操作系统中,系统会个每个线程分配CPU时间片,一旦CPU时间片结束就会将当前线程换到下个线程,即使该线程没有结束。
可以总结出使线程处于就绪状态的几种方法:
1.调用sleep()方法;
2.调用wait()方法;
3等待输入/输出完成
当线程处于就绪状态后,可以用以下方法使线程再次进入运行状态:
1.调用notify()方法;
2.调用notifyall方法;
3.调用interrupt()方法;
4.休眠时间结束;
5.输入/输出结束;
五、操作线程的方法
(一)sheep()方法;
sheep()方法需要一个参数用于指定该线程的休眠时间,该时间以毫秒为单位。由于sleep()方法的执行有可能抛出InterruptedException异常,所以将sleep()方法的调用放在try_catch语句中,虽然使用了sleep()方法的线程在一段时间内会醒来,但并不保证他醒来后进入运行状态,只能保证他进入就绪状态。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
(二)join()方法;
如果当前程序为多线程程序,假如存在一个线程A,现在需要插入线程B,并要求B先执行完,然后继续执行A,此时可以使用Thread类中join()方法来完成。
以下一个实例为上面的进度条要等待下面的进度条完成才可以继续。
public class JoinTest extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private Thread threadA; // 定义两个线程
private Thread threadB;
final JProgressBar progressBar = new JProgressBar(); // 定义两个进度条组件
final JProgressBar progressBar2 = new JProgressBar();
int count = 0;
public static void main(String[] args) {
init(new JoinTest(), 100, 100);
}
public JoinTest() {
super();
// 将进度条设置在窗体最北面
getContentPane().add(progressBar, BorderLayout.NORTH);
// 将进度条设置在窗体最南面
getContentPane().add(progressBar2, BorderLayout.SOUTH);
progressBar.setStringPainted(true); // 设置进度条显示数字字符
progressBar2.setStringPainted(true);
// 使用匿名内部类形式初始化Thread实例子
threadA = new Thread(new Runnable() {
int count = 0;
public void run() { // 重写run()方法
while (true) {
progressBar.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程A休眠100毫秒
threadB.join(); // 使线程B调用join()方法
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
threadA.start(); // 启动线程A
threadB = new Thread(new Runnable() {
int count = 0;
public void run() {
while (true) {
progressBar2.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程B休眠100毫秒
} catch (Exception e) {
e.printStackTrace();
}
if (count == 100) // 当count变量增长为100时
break; // 跳出循环
}
}
});
threadB.start(); // 启动线程B
}
// 设置窗体各种属性方法
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
(三)interrupt()方法;
不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
public class Thread_interrupt {
public static void main(String[] args) {
ClassRoom room = new ClassRoom();
room.student.start();
room.teacher.start();
}
}
class ClassRoom implements Runnable{
Thread student,teacher;
ClassRoom(){
teacher = new Thread(this);
student = new Thread(this);
teacher.setName("雷老师");
student.setName("张爱睡");
}
@Override
public void run() {
if(Thread.currentThread() == student) {
try {
System.out.println(student.getName()+"正在睡觉,不听课");
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
System.out.println(student.getName()+"被老师叫醒了");
}
System.out.println(student.getName()+"开始听课");
}
else if(Thread.currentThread() == teacher) {
for(int i = 0; i <= 3; i++ ) {
System.out.println("上课");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
student.interrupt();
}
}
}
(四)wait()方法
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
(五)yield()方法
Thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知他可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
六、线程优先级
调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
七、线程同步
在单线程程序中,每次只能做一件事,后面的事情需要等待前面 的事情完成后才可以进行,但是使用多线程时,就会发生两个线程抢占资源的问题,所以在多线程编程中需要防止资源访问的冲突。Java提供了线程同步的机制来防止资源访问的冲突。
下面来看一个火车售售票例子;
public class ThreadSafeTest implements Runnable {
int num = 10; // 设置当前总票数
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + num--);
}
}
}
public static void main(String[] args) {
ThreadSafeTest t = new ThreadSafeTest(); // 实例化类对象
Thread tA = new Thread(t); // 以该类对象分别实例化4个线程
Thread tB = new Thread(t);
Thread tC = new Thread(t);
Thread tD = new Thread(t);
tA.start(); // 分别启动线程
tB.start();
tC.start();
tD.start();
}
}
运行出来最后 剩下的票为负值,这是因为同时创建4个线程,这4个线程执行run()方法,在num变量为1时,线程1、2、3、4都对num变量具有储存功能,当线程1执行run()方法时,还没来得及做递减操作,就指定它调用sleep()方法进入就绪状态,这时线程2、3、4都进入run()方法,发现num值依然大于0,但此时线程1休眠时间已到,将num变量值递减,同时线程2、3、4也都对nun进行递减操作,从而产生了负值。
如何解决资源共享问题呢?这时就需要给共享资源加上一把锁。在Java中提供了同步机制,可以有效防止资源冲突,同步机制使用关键字synchronized。
1.同步块
public class ThreadSafeTest implements Runnable {
int num = 10; // 设置当前总票数
public void run() {
while (true) {
synchronized(""){
if (num > 0) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + num--);
}
}
}
}
public static void main(String[] args) {
ThreadSafeTest t = new ThreadSafeTest(); // 实例化类对象
Thread tA = new Thread(t); // 以该类对象分别实例化4个线程
Thread tB = new Thread(t);
Thread tC = new Thread(t);
Thread tD = new Thread(t);
tA.start(); // 分别启动线程
tB.start();
tC.start();
tD.start();
}
}
2.同步方法
语法:synchronized void f(){}
当某个对象调用了同步方法,该对象的其他方法必须等待该同步方法执行完毕后才执行。