这几天开始学习java并发编程实战,发现这本书理论知识很多,实践并不多,而且基本知识貌似默认大家是知道的。所以边看边学习一下基础知识。
1.首先看下官方API文档的介绍:
- JVM允许在一个程序中同时有多个运行的线程。
- 每个线程都有一个优先级(priority),优先级高的线程运行时要优先于优先级低的线程,每个线程要么是守护线程,要么不是。当一个线程在运行时创建另一个线程是,新线程最初的优先级和创建它的线程一样。一个线程是守护线程当且仅当创建它的线程是守护线程。
-创建线程有两种方法,一种是继承Thread类并实现Run()方法,另一种是实现Runnable接口并实现run方法。
2.第一种方法:继承Thread类并实现run方法
public class MyThread extends Thread {
//可以指定线程的名字
public MyThread(String threadName){ super(threadName); }
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
//请求让出对CPU的占用,即主动请求切换线程,但是实际切换不切换要看CPU心情
Thread.yield();
}
}
public static void main(String[] agrs) {
new MyThread("thread 1").start();
new MyThread("thread 2").start();
new Thread().start();
}
}
result:
thread 2 0
thread 1 0
thread 2 1
thread 1 1
thread 2 2
thread 1 2
thread 2 3
thread 1 3
thread 2 4
thread 1 4
这里main方法中最后的线程就是Thread类本身而不是它的子类,虽然文档上讲需要实现Run方法,但Run方法Thread实现Runnable方法后必须实现的,并不是abstract方法,不实现也不会报错,但是不实现的话这个线程其实啥都不会做,因为Run方法是空的。
因为main中两个线程的优先级是相同的,所以执行顺序并不确定,每次运行的结果都是随机的。
Thread类的构造器有:
Thread()
Thread(Runnable target) //指定Runnable,与后面第二种方法有关
Thread(String name) //指定线程的名称
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target) //指定线程组
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
3.第二种方法:实现Runable接口,并实现run方法
class MyThread implements Runnable {
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
Thread.yield();
}
}
public static void main(String[] args) {
new Thread(new MyThread(),"thread 1").start();
new Thread(new MyThread(),"thread 2").start();
}
}
result:
thread 2 0
thread 1 0
thread 1 1
thread 1 2
thread 1 3
thread 1 4
thread 2 1
thread 2 2
thread 2 3
thread 2 4
在这里因为是实现接口,所以就必须实现Run()方法了。
4.特别方法:匿名内部类
public class MyThread {
public static int num = 1;
public static void getNum() {
for(int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName() + " " + num++);
Thread.yield();
}
}
public static void main(String[] args){
Thread thread1 = new Thread(new Runnable() { public void run() { MyThread.getNum();} },"thread1");
Thread thread2 = new Thread(new Runnable() { public void run() { MyThread.getNum();} },"thread2");
thread1.start();
thread2.start();
try {
thread1.join();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(MyThread.num);
}
}
result:
thread1 1
thread2 1
thread1 2
thread2 3
thread1 4
thread2 5
thread1 6
thread2 7
thread1 8
thread2 9
10
这里其实还是第二种方法,只不过是用匿名内部类实现。但是仔细观察输出的结果,thread 1和thread 2 都输出一个num = 1(这是程序重复运行好几次才偶尔出现的)。这个因为我的MyThread类并不是线程安全的,当都多个线程同时访问这个类的数据并且数据有更改时,可能得到错误的数据。这是下一篇线程安全的问题啦。
4.总结
其实构造线程无非就是继承和实现接口两种方法,但是具体的形式可以有很多种,比如第三种方法,但是万变不离其宗。另外,多线程编程一定和线程安全和锁是紧密联系。
Thread类的方法由于现在还不需要用到,所以以后在总结。官方API文档对方法介绍的都很好,比如虽然有destroy方法,但是这个方法在现实编程中不能使用,因为他会产生线程死锁等危害。