前记:师夷长技以自强
1.JDK中支持的类和接口
Thread:
线程类的声明部分如下:
public class Thread
extends Object
implements Runnable
可以看出Thread是一个直接继承自Object的类,并且实现了Runnable接口。
根据JDK文档可知,创建一个线程的方式有两种:
(1)继承Thread类;
(2)把实现Runnable接口的类的对象作为Thread类构造函数参数。
ex1(继承):
public class ThreadInheri {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("MyThread running!");
}
}
output:
MyThread running!
ex2(接口):
public class ThreadImp {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
new Thread(myThread1).start();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("MyThread1 running!");
}
}
output:
MyThread1 running!
Runnable接口
当欲创建的进程类已经有了继承的父类时,由于java语言的单继承性就,此时可以通过实现Runnable接口来创建线程类。
注:Thread类作为实现了RUnnable接口的类也可以传递给Thread类的构造函数,此时实际上是将一个Thread对象中的run方法交由其他的线程进行调用。
2.线程的特性
启动的随机性
执行start()方法的顺序不代表线程启动的顺序
ex3:
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread(1);
MyThread t2 = new MyThread(2);
MyThread t3 = new MyThread(3);
MyThread t4 = new MyThread(4);
MyThread t5 = new MyThread(5);
MyThread t6 = new MyThread(6);
MyThread t7 = new MyThread(7);
MyThread t8 = new MyThread(8);
MyThread t9 = new MyThread(9);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
t9.start();
}
}
class MyThread extends Thread{
private int i;
public MyThread(int i) {
super();
this.i = i;
}
@Override
public void run() {
super.run();
System.out.println(i);
}
}
output:
4
8
2
6
1
5
9
3
7
运行的异步性
多线程(包括main线程)并发执行的过程中相互竞争处理器运行。
ex4:
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("myThread");
thread.start();
try {
for (int i = 0; i < 10; i++) {
int time = (int) (Math.random() * 1000);
Thread.sleep(time);
System.out.println("main="+Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread{
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
int time = (int) (Math.random() * 1000);
Thread.sleep(time);
System.out.println("run=" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
main=main
run=myThread
main=main
run=myThread
run=myThread
main=main
run=myThread
main=main
run=myThread
run=myThread
main=main
main=main
run=myThread
main=main
run=myThread
main=main
run=myThread
main=main
main=main
run=myThread
注:使用start方法调用线程是异步的,其本质是把线程改为就绪状态,但是否马上运行还得看线程调度器。如果直接调用run方法,则线程的运行时同步的。
3.线程安全
非线程安全
非线程安全主要是指多个线程对同一个对象中的同一个实力变量进行操作时会出现值被更改、值不同步的情况。
ex5:
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread, "A");
Thread b = new Thread(myThread, "B");
Thread c = new Thread(myThread, "C");
Thread d = new Thread(myThread, "D");
Thread e = new Thread(myThread, "E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
count--;
System.out.println(currentThread().getName()+":count="+count);
}
}
output:
C:count=3
A:count=3
D:count=2
B:count=1
E:count=0
在上例中,因为多个线程都改写了变量count,导致线程C和A访问时引发线程安全问题。其原因是线程C输出的值是经过线程C和A修改的,也就是线程C在执行run方法时被线程A的run方法所打断。
synchronized使用此关键字可以把run方法变为临界区,一次只允许一个访问,把异步执行变为同步执行。如下所示:
ex6:
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread, "A");
Thread b = new Thread(myThread, "B");
Thread c = new Thread(myThread, "C");
Thread d = new Thread(myThread, "D");
Thread e = new Thread(myThread, "E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run() {
count--;
System.out.println(currentThread().getName()+":count="+count);
}
}
4.相关API
currentThread
返回代码段正在被哪个线程调用的信息。
ex7:
class MyThread extends Thread{
public MyThread() {
System.out.println("MyThread Constructed:"+currentThread().getName());
}
@Override
public void run() {
super.run();
System.out.println("MyThread run:"+currentThread().getName());
}
}
public class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
output:
MyThread Constructed:main
MyThread run:Thread-0
可以看到构造函数是被main线程调用的,run函数是被Thread-0线程调用的。
isAlive
测试线程是否属于激活状态,即已启动尚未终止的状态。
sleep
在指定的毫秒数内让当前“正在执行的线程”(this.currentThread()返回的线程)休眠。
getId
取得线程的唯一标识。
4.1停止线程
this.interrupted测试当前线程是否已中断(运行this.interrrupted方法的线程)。值得注意的是,这个方法被调用后会清除线程的中断状态。如下所示:
ex8:
public class Test{
public static void main(String[] args){
Thread.currentThread().interrupt();
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
}
}
output:
true
false
isInterrupted
测试制定的线程是否已经停止,这个方法是不清除线程的中断状态的。如下所示:
ex9:
class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 50000; i++) {
System.out.println("i="+(i+1));
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(100);
myThread.interrupt();
System.out.println("is interrupted? "+myThread.isInterrupted());
System.out.println("is interrupted? "+myThread.isInterrupted());
}
}
output:(只显示了部分)
is interrupted? true
is interrupted? true
那么,究竟如何停止线程呢?
异常法
ex10:
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if(this.interrupted()){
System.out.println("I am interrupted!!!");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
System.out.println("I am under for loop.");
} catch (InterruptedException e) {
System.out.println("I come into catch!!!");
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
myThread.interrupt();
}
}
output:(部分)
i=75920
I am interrupted!!!
I come into catch!!!
暴力停止
ex11
class MyThread extends Thread{
private int i = 0;
@Override
public void run() {
super.run();
try {
while (true){
i++;
System.out.println("i="+i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args){
try {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(8000);
myThread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
需要指出的是stop方法已经被废弃,一来强行关闭线程导致某些清理工作不能进行,二来对锁定的对象进行了“解锁”而导致数据得不到同步处理。比如下面的情况:
ex12
class SynchronizedObject{
private String username="a";
private String password="aa";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
synchronized public void printString(String username, String password){
try {
this.username = username;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread{
private SynchronizedObject object;
public MyThread(SynchronizedObject object) {
this.object = object;
}
@Override
public void run() {
object.printString("b","bb");
}
}
public class Test{
public static void main(String[] args){
try {
SynchronizedObject object = new SynchronizedObject();
MyThread myThread = new MyThread(object);
myThread.start();
Thread.sleep(500);
myThread.stop();
System.out.println(object.getUsername()+" "+object.getPassword());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
b aa
return法
ex13:
class MyThread extends Thread{
@Override
public void run() {
while (true){
if(this.isInterrupted()){
System.out.println("I am interrupted!!!");
return;
}
System.out.println(System.currentTimeMillis());
}
}
}
public class Test{
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:(部分)
1591368524361
1591368524362
I am interrupted!!!
4.2暂停线程
暂停的线程可以继续恢复,可以使用suspend()暂停线程,使用resume()恢复线程的执行。
ex14:
import org.omg.PortableServer.THREAD_POLICY_ID;
class MyThread extends Thread{
private long i = 0;
public long getI() {
return i;
}
public void setI(long i) {
this.i = i;
}
@Override
public void run() {
while (true){
i++;
}
}
}
public class Test{
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(5000);
myThread.suspend();
System.out.println("A="+System.currentTimeMillis()+" i="+myThread.getI());
Thread.sleep(5000);
System.out.println("A="+System.currentTimeMillis()+" i="+myThread.getI());
myThread.resume();
Thread.sleep(5000);
myThread.suspend();
System.out.println("B="+System.currentTimeMillis()+" i="+myThread.getI());
Thread.sleep(5000);
System.out.println("B="+System.currentTimeMillis()+" i="+myThread.getI());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
A=1591406886243 i=1860497709
A=1591406891243 i=1860497709
B=1591406896243 i=3817579030
B=1591406901244 i=3817579030
可以看到程序被成功暂停了,并且可以恢复成运行状态。
独占然而suspend和resume是被废除的两个方法,因为可能会引起共享的同步对象的独占,如下:
ex14:
class SynchronizedObject{
synchronized public void printString(){
System.out.println("begin!");
if(Thread.currentThread().getName().equals("a")){
System.out.println("a thread suspend forever!");
Thread.currentThread().suspend();
}
System.out.println("end!");
}
}
public class Test{
public static void main(String[] args) {
try {
SynchronizedObject object = new SynchronizedObject();
Thread thread1 = new Thread() {
@Override
public void run() {
object.printString();
}
};
thread1.setName("a");
thread1.start();
Thread.sleep(1000);
Thread thread2 = new Thread() {
@Override
public void run() {
System.out.println("Thread2 start");
object.printString();
}
};
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
begin!
a thread suspend forever!
Thread2 start
由上面的结果可以看出,当thread2执行run方法时是不能进入object.printString的。在java中println函数内部自带同步锁,使用suspend也有可能造成其同步锁未被释放,如下
ex15:
public class Test{
public static void main(String[] args) {
try {
Thread thread = new Thread() {
private long i = 0;
@Override
public void run() {
while (true) {
i++;
System.out.println(i);
}
}
};
thread.start();
Thread.sleep(1000);
thread.suspend();
System.out.println("main end!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:(部分)
85552
85553
85554
可以看到main函数中的打印语句没有被执行。
不同步
如果suspend出现在事务操作中间,则会引起值的不同步现象。
4.3主动让CPU
yield方法作用是放弃当前的CPU资源,并从新参与cpu的竞争。在下例中,yield的频繁调用让cpu频繁让出,使程序的运行时间延长。
ex16:
class MyThread extends Thread{
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 500000000; i++) {
count = count + i;
}
long endTime = System.currentTimeMillis();
System.out.println("time cost:"+(endTime - beginTime)+"ms");
}
}
public class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
output:
time cost:236ms
加上yield后
ex17:
class MyThread extends Thread{
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 500000000; i++) {
Thread.yield();
count = count + i;
}
long endTime = System.currentTimeMillis();
System.out.println("time cost:"+(endTime - beginTime)+"ms");
}
}
public class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
output:
time cost:102355ms
4.4优先级
线程优先级存在继承。
setPriority设置线程优先级
getPriority获取线程优先级
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("MyThread1 run priority="+this.getPriority());
new MyThread2().start();
}
}
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println("MyThread2 run priority="+this.getPriority());
}
}
public class Test{
public static void main(String[] args) {
System.out.println("main run priority="+Thread.currentThread().getPriority());
Thread.currentThread().setPriority(6);
System.out.println("main run priority="+Thread.currentThread().getPriority());
MyThread1 myThread1 = new MyThread1();
myThread1.start();
}
}
output:
main run priority=5
main run priority=6
MyThread1 run priority=6
MyThread2 run priority=6
4.5守护线程
守护线程为其他线程服务,若此时没有非守护线程了,则自动销毁。
Daemon
class MyThread extends Thread{
private int i = 0;
@Override
public void run() {
try {
while (true){
i++;
System.out.println("i="+(i));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("haha!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
i=1
i=2
i=3
i=4
i=5
haha!
5.总结
本文先后讲了线程的基本概念、特性、线程安全还有JDK对线程操作的支持API,这些都是线程使用的基础。而线程安全是并发的重中之重,下一篇将讲如何对对象和变量并发访问。