在现实世界中很多事情是并行发生的,这就说明计算机要想正确的模拟现实世界,一定要解决程序的“并行”问题。这就引出了程序设计中的一个非常重要的技术——多线程。幸运的是Java语言内置了对多进程的支持。
A)概念
线程是程序内部的顺序控制流,是程序中的一条执行路径。每个线程都是一个能独立执行自身指令的不同控制流,每个线程有其自身的产生、存在和消亡的过程,线程是一个动态的概念。多线程是程序中包含多条执行路径。在同一个程序中可以运行多个线程来执行不同的任务,多个线程并行的完成自身的任务。多线程是实现并发的一个有效手段。
B)线程的状态
一个线程从创建到消亡的过程称为线程的生命周期。在一个完整的生命周期中,线程的状态转换如下图所示。
C)线程的创建
a)从Thread类继承
1)创建Thread类的子类SubThread,并重写run()方法。一般格式为:
public class SubThread extends Thread{
@Override
public void run() {
//to do...
}
}
2)在主方法main中创建子线程对象SubThread。一般格式为:
SubThread st = new SubThread();
3)启动线程。一般格式为:
st.start();
b)实现Runnable接口
1)定义实现Runnable接口的类Thread_Runnable。一般格式:
class Thread_Runnable implements Runnable{
@Override
public void run() {
//to do...
}
}
2)创建实现Runnable接口的类Thread_Runnable对象tr。一般格式:
Thread_Runnable tr = new Thread_Runnable();
3)创建线程thread,用2)中实例化的对象tr构造。一般格式:
Thread thread = new Thread(tr);
4)启动线程。一般格式为:
thread.start();
PS:由于Java是单根继承,因此一般推荐使用第二种方法创建新线程。
D)线程控制基本方法
1)sleep();这是Thread类的一个静态方法,因此可以通过类名直接调用,并且在哪个线程中调用哪个线程处于阻塞状态。调用sleep()后当前线程虽然会暂停一个指定的时间,但不会释放对象锁。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2)join();实现线程合并。这个方法语言不太好描述直接看示例就很简单了。
1 public class Main { 2 3 public static void main(String[] args) throws InterruptedException { 4 Thread_t t = new Thread_t(); 5 t.start(); 6 t.join();//请注消此行,对比结果 7 int i = 0; 8 while(8 != i){ 9 System.err.println(i++ +"_main Thread is running"); 10 Thread.sleep(2000); 11 } 12 13 } 14 } 15 16 class Thread_t extends Thread { 17 static int i = 0; 18 19 @Override 20 public void run() { 21 while (8 != i) { 22 System.out.println(i++ + "_subThread is running"); 23 try { 24 sleep(2000); 25 } catch (InterruptedException e) { 26 return; 27 } 28 } 29 } 30 31 }
执行结果:
然后,请注销代码中的第6行,重新测试执行结果。结果如下:
由此,我们可以知道调用join()方法后程序只有一条执行路径,只有当子线程的run执行完后才会接着执行主进程下面的代码,有点类似于方法的调用。
3、yield()
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
对于这个yield()方法的学习我看了一个尚学堂马老师的视频,但同样的代码在我电脑上却得不到正确的结果很是无解:下面把我的疑惑写出来大家一起来看看。
public class TestYield { public static void main(String[] args) { MyThread3 t1 = new MyThread3("t1"); MyThread3 t2 = new MyThread3("t2"); t1.start(); t2.start(); } } class MyThread3 extends Thread { MyThread3(String s){super(s);} public void run(){ for(int i =1;i<=100;i++){ System.out.println(getName()+": "+i); if(i%10==0){ yield(); } } } }
上面是老师的代码。老师的执行结果如下:
老师的执行结果就是每逢10的倍数就一定会切换线程。这也是我想看到的结果,但我却得不到。
同样的代码在我电脑上的执行结果如下:
可以看到我的到10了,也不一定就会切换,试了多次都是如此。在群里讨论的结果:大概是yield方法对现在的“抢占式”OS已失消。老师的视频录自很多年前。但我自己心里还是有点不太相信这种解释。在此写出来希望集大家之智能给出一个合适的解释。
最后,再补充一点:网上有一种说法是:yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
欢迎讨论!
E)线程同步
synchronized不是方法而是Java语言的一个关键字,该关键字用于保护共享数据。每个对象都有一个锁标志,当一个线程访问到该对象,被Synchronized修饰的数据将被"上锁",阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了。请看示例:
public class TestSync implements Runnable { Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } public void run(){ timer.add(Thread.currentThread().getName()); } } class Timer{ private static int num = 0; public synchronized void add(String name){ num ++; try {Thread.sleep(1);} catch (InterruptedException e) {} System.out.println(name+", 你是第"+num+"个使用timer的线程"); } }
在上述代码中的Timer类中有一个方法add,首先不给此方法加synchronized,运行结果如下:
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
显然这种结果和我们所希望的不一样,其时这就是由于t1线程在运行到num++后被打断所致。我们知道线程是通过“时间片”轮转获得cpu,在实际中完全有可能刚好运行完num++后当前线程时间片用完,我们在此用sleep只是为了放大这种效应。所以,为了防止这种情况发生我们应该想办法拒绝别的线程访问在当前线程完成此操作之前来访问此资源,Java中的synchronized就是为此而生。我们加上synchronized后,运行结果如下:
t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程
F)、线程死锁
有了synchronized可以有效的防止多线程同时对某共享资源访问所产生的“数据不一致性错误”,但synchronized会带来新的问题——线程死锁。请看下面的示例:
1 public class Main implements Runnable { 2 3 public Main(int flag) { 4 this.flag = flag; 5 } 6 7 int flag = 1; 8 static Object a = new Object(); 9 static Object b = new Object(); 10 11 @Override 12 public void run() { 13 System.out.println("before"); 14 if (flag == 0) { 15 synchronized (a) { 16 try { 17 Thread.sleep(1000); 18 } catch (InterruptedException e) { 19 // TODO Auto-generated catch block 20 e.printStackTrace(); 21 } 22 synchronized (b) { 23 System.out.println("flag = " + flag); 24 } 25 } 26 27 } else { 28 synchronized (b) { 29 try { 30 Thread.sleep(1000); 31 } catch (InterruptedException e) { 32 // TODO Auto-generated catch block 33 e.printStackTrace(); 34 } 35 synchronized (a) { 36 System.out.println("flag = " + flag); 37 } 38 39 } 40 41 } 42 } 43 44 // ************************************************************************* 45 public static void main(String[] args) { 46 Thread t1 = new Thread(new Main(0)); 47 Thread t2 = new Thread(new Main(1)); 48 t1.start(); 49 t2.start(); 50 } 51 52 }
程序运行结果:
由此结果我们可以知道t1,t2线程已陷入死锁状态。关于线程的同步与死锁问题比较复杂,等整理好了,我会单独写一个随笔与大家讨论。