——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
一、概念与原理
线程是指进程中的一个执行流程,一个进程可以运行多个线程。
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
二、线程的创建
线程的创建方式一共有两种:继承和接口实现
- 继承Thread方法
在java中已经提供了对线程这类事物的描述的类——Thread类。
创建线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,通过创建Thread类的子类对象,创建线程对象;
4,调用线程的start方法,开启线程,并执行run方法。
此类中有个run()方法,应该注意其用法:
public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行 操作并返回。
Thread构造函数有许多:
public Thread( );
public Thread(Runnable target);
public Thread(String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target);
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize);
下面是一个实例:
public class ThreadDemo {
public static void main(String[] args){
Demo d = new Demo();
d.start();//开启线程并执行该线程的run方法
for(int i=0;i<60;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class Demo extends Thread{ //继承Thread的方法
public void run(){ //复写run方法
for(int i=0;i<60;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
当启动这个线程的时候,会调用这个线程的run方法,其实不然,当我们这个run方法是覆盖父类的run方法的时候:
public void run() {
if (target != null) {
target.run();
}
}
也就会是说,Thread继承的会先从父类中判断target是否为空,这个target是runnable接口。因此如果不target不为空的话,会执行runnable中的run方法,而不是去执行thread本身的run方法。
实例如下:
package com.enterise.test.thread;
public class TestThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
new Thread().sleep(500);
System.out.println("--runnable->"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}){
@Override
public void run(){
while(true){
try {
new Thread().sleep(500);
System.out.println("--thread.run->"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
我们会看到结果:执行runnable中的run代码。
- 实现Runnable接口
那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就必须用这个方法:实现Runnable接口,并复习其中run方法的方式。
步骤:
1.定义类实现接口Runnable接口。
2.覆盖Runnable接口中的run方法。 将线程要运行的代码存放在该run方法中。
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
下面是一个实例:
public class ThreadDemo2 {
public static void main(String[] args){
Demo2 d =new Demo2();
Thread t = new Thread(d);
t.start();
for(int x=0;x<60;x++){
System.out.println(Thread.currentThread().getName()+x);
}
}
}
class Demo2 implements Runnable{
public void run(){
for(int x=0;x<60;x++){
System.out.println(Thread.currentThread().getName()+x);
}
}
}
在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属对象。
获取当前线程的对象的方法是:Thread.currentThread();
三、线程的状态
线程在一共有5种状态:
被创建:线程对象已经创建,还没有在其上调用start()方法。
运行:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式;线程释放了执行权,同时释放执行资格;
临时阻塞状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop();当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出.
这几个状态的关系图如下:
态切换的所有函数如下:
start();启动线程执行run方法。
sleep();线程执行时,遇到sleep,者该线程执行睡眠,当睡眠时间达到的时候,会自动在执行下去。
wait();等待,使该线程等待着,进入线程池中。
notify();唤醒线程。(线程池:先进先出,唤醒先进去的线程)唤醒一个线程。
notifyAll();唤醒所有线程(线程池中的所有线程)
stop();停止线程。
run();方法执行结束。消亡。
- 睡眠
睡眠是对线程阻止的一种可能,使用Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行)。
因为Java规范不保证合理的轮换,所以当线程执行太快,或者需要强制进入下一轮时,我们可以使用睡眠的方法。
下面是一个实例:
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("-------" + i);
}
System.out.print(i);
try {
Thread.sleep(1);
System.out.print(" 线程睡眠1毫秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyThread().star();
}
} //该运行的结果为:一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串.
要注意的是:sleep()是静态方法,只能控制当前正在运行的线程。并且程睡眠到期自动苏醒,并返回到可运行状态,但不一定会立刻执行。
- 线程让步
线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
线程可以通过setPriority(int newPriority)更改线程的优先级。
通常默认的优先级是5,Thread类中有三个常量定义了优先级。
static int MAX_PRIORITY //线程可以具有的最高优先级。
static int MIN_PRIORITY //线程可以具有的最低优先级。
static int NORM_PRIORITY // 分配给线程的默认优先级。
Thread.yield();的方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态。
而实际的操作中,yield()会让当前运行线程回到可运行状态,并且一起参与获得运行机会。而根据优先级进行获得运行机会,所以使用yield方法也未必会让步。
下面是一个生产者消费者和yield的例子:
public class yieldDemo
{
public static void main(String[] args)
{
Thread producer = new Producer();
Thread consumer = new Consumer();
producer.setPriority(Thread.MIN_PRIORITY); //生产者设置最低优先级
consumer.setPriority(Thread.MAX_PRIORITY); //消费者设置最高优先级
producer.start();
consumer.start();
}
}
class Producer extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println("I am Producer : Produced Item " + i);
Thread.yield();
}
}
}
class Consumer extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println("I am Consumer : Consumed Item " + i);
Thread.yield();
}
}
}
在有了yield后输出结果为:
I am Producer : Produced Item 0
I am Consumer : Consumed Item 0
I am Producer : Produced Item 1
I am Consumer : Consumed Item 1
I am Producer : Produced Item 2
I am Consumer : Consumed Item 2
I am Producer : Produced Item 3
I am Consumer : Consumed Item 3
I am Producer : Produced Item 4
I am Consumer : Consumed Item 4
3. 线程中断
线程中断有stop()和interrupt()方法。stop()方法会直接终止一个线程,并且回收部分资源。然而,这种方法是不安全也是不受提倡的。一般使用interrupt()来中断,然而,这种方法并不会中断一个正在运行的线程。
class Demo implements Runnable{
public void run() {
while (true) {
System.out.println("I am running!");
for (int i = 0; i < 900000; i++) {}
//给线程调度器可以切换到其它进程的信号
Thread.yield();
}
}
}
public class InterruptTaskTest {
public static void main(String[] args) throws Exception{
//将任务交给一个线程执行
Thread t = new Thread(new Demo());
t.start();
//运行一断时间中断线程
Thread.sleep(100);
System.out.println("Interrupted Thread!");
t.interrupt();
}
}
然而这段代码后我们发现并没有终止程序,所以要Thread.interrupted()来判断是否继续运行来终止。代码如下:
class Demo implements Runnable{
public void run() {
try {
//检查程序是否发生中断
while (!Thread.interrupted()) {
System.out.println("I am running!");
Thread.sleep(20);
for (int i = 0; i < 900000; i++) ;
}
}
System.out.println("Demo.run() interrupted!");
}
}
四、多线程的安全性
由于发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。
如下面一个计算的例子:
/* 这个类中的count方法是计算1一直加到10的和,并输出当前线程名和总和,我们期望的是每个线程都会输出55*/
public class Count {
private int num;
public void count() {
for(int i = 1; i <= 10; i++) {
num += i;
}
System.out.println(Thread.currentThread().getName() + "-" + num);
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
Count count = new Count();
public void run() {
count.count();
}
};
for(int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
输出的结果并没有预期的那样,而是不同的数字。
这样就是多个线程不安全,因此想要改变这个情况就必须使用同步代码块或者加锁。我会在下一篇中详细说明。