Java中的多线程
进程:进程是操作系统的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上的运行过程,它是系统进行资源分配和调度的一个独立单位。
线程:线程可以理解为是在进程中独立运行的子任务。
当一个类中需要用到多线程时,一方面可以直接继承Thread类,另一方面可以去实现Runnable接口,这两种方法的作用其实是一样的,因为Thread类本身实现了Runnable接口,不过鉴于Java是单继承的特性,所以在这里,建议大家通过第二种方法去使用多线程。
获取当前线程名称的方法:Thread.currentThread().getName()
取得线程的唯一标识:getId()方法
线程放弃当前CPU资源:yield()方法,将此机会让给其他的任务去占用CPU执行时间。
多线程的一些特点 <咱们看例子说话>
public class Thread1Test extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("MyThread");
}
public static void main(String[] args) {
Thread1Test thread1Test=new Thread1Test();
//开启线程,实现异步操作,如果直接调用run(),则不会产生异步的效果
thread1Test.start();
System.out.println("fdjvdfdkljg");
}
}
其运行结果为:
fdjvdfdkljg
MyThread
1.代码的运行结果与代码执行顺序或调用顺序是无关的,这种执行的随机性表现出CPU执行哪个线程具有不确定性。
public class Thread1Test extends Thread{
private int i;
public Thread1Test(int i){
this.i=i;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(i);
}
public static void main(String[] args) {
Thread1Test thread1Test1=new Thread1Test(1);
Thread1Test thread1Test2=new Thread1Test(2);
Thread1Test thread1Test3=new Thread1Test(3);
Thread1Test thread1Test4=new Thread1Test(4);
Thread1Test thread1Test5=new Thread1Test(5);
Thread1Test thread1Test6=new Thread1Test(6);
thread1Test1.start();
thread1Test2.start();
thread1Test3.start();
thread1Test4.start();
thread1Test5.start();
thread1Test6.start();
}
}
其执行结果为:
1
4
5
6
2
3
2. 当一个程序中存在多个start()方法时,执行start()方法的顺序不代表线程启动的顺序。
3. 自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这个特点在多线程之间进行交互时是很重要的一个技术点。
1)不共享数据
public class Thread1Test extends Thread{
private int count=5;
public Thread1Test(String name){
this.setName(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
while(count>0){
count--;
System.out.println("由"+this.currentThread().getName()+
"计算,"+"count="+count);
}
}
public static void main(String[] args) {
Thread1Test thread1Test1=new Thread1Test("A");
Thread1Test thread1Test2=new Thread1Test("B");
Thread1Test thread1Test3=new Thread1Test("C");
thread1Test1.start();
thread1Test2.start();
thread1Test3.start();
}
}
执行结果为:
由B计算,count=4
由C计算,count=4
由A计算,count=4
由C计算,count=3
由B计算,count=3
由C计算,count=2
由A计算,count=3
由C计算,count=1
由B计算,count=2
由C计算,count=0
由A计算,count=2
由A计算,count=1
由A计算,count=0
由B计算,count=1
由B计算,count=0
- 从结果中我们会发现,对于每一个线程,都会有独立的一个count,这种情况就是数据不共享的情况。
2)共享数据
public class Thread1Test extends Thread{
private int count=5;
@Override
public void run() {
// TODO Auto-generated method stub
while(count>0){
count--;
System.out.println("由"+this.currentThread().getName()+
"计算,"+"count="+count);
}
}
public static void main(String[] args) {
Thread1Test thread1Test1=new Thread1Test();
Thread t1=new Thread(thread1Test1, "A");
Thread t2=new Thread(thread1Test1, "B");
Thread t3=new Thread(thread1Test1, "C");
t1.start();
t2.start();
t3.start();
}
}
运行结果为:
由B计算,count=3
由A计算,count=3
由B计算,count=2
由A计算,count=1
由B计算,count=0
- 从结果中我们会发现,A与B同时对count进行了操作,所以count=3,一共出现了两次,这里也就产生了非线程安全的问题(
- 非线程安全主要是指多个线程对同一个对象的同一个变量进行操作,使得变量的值不能够同步的情况)这里其实我们会发现其实count数据已经被三个线程所共享了,但由于出现了非线程安全的问题,所以此时的结果离我们想要的最终结果(count 由5到0依次递减)是有一定的差距。 为此,我们对程序进行了修改,如下所示:
public class Thread1Test extends Thread{
private int count=5;
@Override
public synchronized void run() {
// TODO Auto-generated method stub
count--;
System.out.println("由"+this.currentThread().getName()+
"计算,"+"count="+count);
}
public static void main(String[] args) {
Thread1Test thread1Test1=new Thread1Test();
Thread t1=new Thread(thread1Test1, "A");
Thread t2=new Thread(thread1Test1, "B");
Thread t3=new Thread(thread1Test1, "C");
t1.start();
t2.start();
t3.start();
}
}
通过在run方法前加synchronized关键字,使得多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用r9un方法前,先判断run方法有没有被上锁,如果上锁,说明其它线程正在调用run方法,此线程必须等其它线程对run方法调用结束后才能执行run方法。这样就实现了排队调用run方法的目的,也就达到了按顺序对count进行减一的效果。synchronized关键字可以在任何对象和方法上加锁,而加锁的这段代码被称为“互斥区”或者“临界区”。
—————————————————————————
有一点需要注意的是,线程操作如果放在System.out.println()中执行的话,会产生非线程安全的问题
public class Thread1Test extends Thread{
private int count=5;
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("由"+this.currentThread().getName()+
"计算,"+"count="+(count--));
}
public static void main(String[] args) {
Thread1Test thread1Test1=new Thread1Test();
Thread t1=new Thread(thread1Test1);
Thread t2=new Thread(thread1Test1);
Thread t3=new Thread(thread1Test1);
Thread t4=new Thread(thread1Test1);
Thread t5=new Thread(thread1Test1);
Thread t6=new Thread(thread1Test1);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
运行结果为:
由Thread-1计算,count=5
由Thread-6计算,count=2
由Thread-4计算,count=3
由Thread-3计算,count=4
由Thread-2计算,count=5
由Thread-5计算,count=1
从结果上我们会发现,非线程安全的问题又一次发生了,为什么会有这种情况呢?看过源码的人都知道println方法中有synchronized关键字修饰,是线程安全的,那为什么还会产生这种错误呢?让我们再仔细看看println方法的源码,看完之后你就懂了。
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
原来println方法的synchronized关键字是在方法体的内部,而我们上边变量的改变是在println方法的参数部分。所以产生了这种非线程安全的情况。
—————————————————————————
线程的停止
停止线程是在多线程开发时很重要的技术点。停止线程意味着在线程处理完任务之前停止掉正在做的操作,也就是放弃当前的操作。
停止一个线程需要用到Thread.stop()方法或者Thread.interrupt()方法,Thread.stop()方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被弃用的。而大多数停止一个线程的操作使用的是Thread.interrupt()方法,这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。单独调用interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
在Java中一共有三种方法可以终止正在运行的线程:
* 使用退出标志,使线程正常退出,也就是当run方法完成之后线程终止。
* 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume方法一样,都是作废过期的方法,使用它们可能产生不可预料的后果。
* 使用interrupt方法中断线程。
1.判断线程是否是停止状态
Thread.java类里提供了两种方法:
1)this.interrupted():测试当前线程(运行此方法的线程)是否已经停止。
2)this.isInterrupted():测试线程是否已经中断。
1.对于this.interrupted()方法,此方法是static的,如果当前的线程处于中断状态,那么调用此方法可以清除线程的中断状态,下面我给出一个例子:
public class Thread1Test extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println("是否停止1?="+Thread.interrupted());
System.out.println("是否停止2?="+Thread.interrupted());
}
}
运行结果为:
是否停止1?=true
是否停止2?=false2.对于isInterrupted()方法,此方法不是static的,此方法只能够测试Thread对象是否处于中断状态,但不清除状态标志。下面,我同样给出一个例子:
public class Thread1Test extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<50000;i++){
System.out.println("i="+(i+1));
}
}
public static void main(String[] args) {
try {
Thread1Test thread1Test=new Thread1Test();
thread1Test.start();
Thread.sleep(2000);
Thread.currentThread().interrupt();
System.out.println("是否停止1?="+Thread.interrupted());
System.out.println("是否停止2?="+Thread.interrupted());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果为:
i=49995
i=49996
i=49997
i=49998
i=49999
i=50000
是否停止1?=true
是否停止2?=false
2.在沉睡中停止线程
1.在sleep状态下停止某一线程:
public class Thread1Test extends Thread {
@Override
public void run() {
try {
System.out.println("run begin");
Thread.sleep(20000);
System.out.println("run end");
}catch (InterruptedException e){
System.out.println("在沉睡中被停止!进入catch"+this.isInterrupted());
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Thread1Test thread1Test=new Thread1Test();
thread1Test.start();
Thread.sleep(200);
thread1Test.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("main catch");
e.printStackTrace();
}
}
}
运行结果为:
run begin
在沉睡中被停止!进入catchfalse
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.company.Thread1Test.run(Thread1Test.java:11)
Process finished with exit code 0
在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false.
2.先用interrupt()停止,然后在sleep:
public class Thread1Test extends Thread {
@Override
public void run() {
try {
for(int i=0;i<10000;i++){
System.out.println("i="+(i+1));
}
System.out.println("run begin");
Thread.sleep(20000);
System.out.println("run end");
}catch (InterruptedException e){
System.out.println("先停止,再遇到了sleep!进入catch");
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Thread1Test thread1Test=new Thread1Test();
thread1Test.start();
Thread.sleep(200);
thread1Test.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("main catch");
e.printStackTrace();
}
}
}
运行结果为:
i=9996
i=9997
i=9998
i=9999
i=10000
run begin
先停止,再遇到了sleep!进入catch
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.company.Thread1Test.run(Thread1Test.java:15)
Process finished with exit code 0
根据运行结果我们可以看到线程先停止,再遇到了sleep!进入catch。
3.使用return停止线程
将方法interrupt()与return相结合也能达到停止线程的效果,测试代码:
public class Thread1Test extends Thread {
@Override
public void run() {
while(true){
if(this.isInterrupted()){
System.out.println("停止了!");
return;
}
System.out.println("time="+System.currentTimeMillis());
}
}
public static void main(String[] args) {
try {
Thread1Test t1=new Thread1Test();
t1.start();
Thread.sleep(2000);
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end!");
}
}
运行结果如下所示:
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
end!
停止了!
线程在return和interrupt()方法的配合下实现了停止。 不过一般不建议使用这种方式来停止线程,因为还有一种更好的方式来停止线程,接下来我就给大家介绍这种方式。
4.使用异常停止线程
public class Thread1Test extends Thread {
@Override
public void run() {
try{
for(int i=0;i<500000;i++){
if(this.interrupted()) {
System.out.println("已经是停止状态了,我要退出");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
System.out.println("我在for下面");
}catch(InterruptedException e){
System.out.println("进入到run方法的catch语句块了");
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Thread1Test t1=new Thread1Test();
t1.start();
Thread.sleep(2000);
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end!");
}
}
运行结果为:
i=384646
i=384647
i=384648
i=384649
end!
已经是停止状态了,我要退出
进入到run方法的catch语句块了
java.lang.InterruptedException
at com.company.Thread1Test.run(Thread1Test.java:15)
此方法可以有效的停止线程,并在catch语句块中将异常向上抛,使得线程停止的事件1得以传播。
暂停线程
暂停线程意味着此线程还可以恢复运行,在java多线程中,可以通过suspend()方法暂停线程,使用resume()方法恢复线程。
1.suspend()与resume()方法的使用
public 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 static void main(String []args){
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(5000);
//A段
thread.suspend();
System.out.println("A="+System.currentTimeMillis()+"i="+thread.getI());
Thread.sleep(5000);
System.out.println("A="+System.currentTimeMillis()+"i="+thread.getI());
//B段
thread.resume();
Thread.sleep(5000);
System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());
Thread.sleep(5000);
System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
A=1490414043916i=2697026143
A=1490414048917i=2697026143
B=1490414053917i=5513982481
B=1490414058917i=8322025282从运行结果可以看到线程确实被暂停了,而且还可以恢复到运行状态
2.suspend()与resume()方法的缺点——独占
在使用suspend()与resume()方法,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。
public class SynchronizeObject {
synchronized public void printString(){
System.out.println("begin");
if (Thread.currentThread().getName().equals("a")){
System.out.println("a线程永远suspend");
Thread.currentThread().suspend();
}
System.out.println("end");
}
public static void main(String []args){
try {
final SynchronizeObject object=new SynchronizeObject();
Thread t1=new Thread(){
@Override
public void run() {
object.printString();
}
};
t1.setName("a");
t1.start();
Thread.sleep(1000);
Thread t2=new Thread(){
@Override
public void run() {
super.run();
System.out.println("t2启动了,但进入不了printString()方法!只打印了一个begin" );
System.out.println("因为printString()方法被a线程锁定并且永远suspend暂停了!");
object.printString();
}
};
t2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
begin
a线程永远suspend
t2启动了,但进入不了printString()方法!只打印了一个begin
因为printString()方法被a线程锁定并且永远suspend暂停了!
从运行结果中我们可以看出,当线程t1执行printString()方法时遇到suspend()被暂停,然后t2就不能再执行printString()方法了。也就造成了公共同步对象的独占。
3.suspend()与resume()方法的缺点——不同步
在使用suspend()与resume()方法时也容易出现因为线程的暂停而导致数据不同步的情况。
public class MyObject {
private String username="l";
private String password="ll";
public void setValue(String u,String p){
this.username=u;
if(Thread.currentThread().getName().equals("a")){
System.out.println("停止a线程");
Thread.currentThread().suspend();
}
this.password=p;
}
public void printUsernamePassword(){
System.out.println(username+" "+password);
}
public static void main(String []args){
try {
MyObject object=new MyObject();
Thread t1=new Thread(){
@Override
public void run() {
super.run();
object.setValue("a","aa");
}
};
t1.setName("a");
t1.start();
Thread.sleep(500);
Thread t2=new Thread(){
@Override
public void run() {
super.run();
object.printUsernamePassword();
}
};
t2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
停止a线程
a ll
从运行结果来看,当在线程执行setValue()方法时,将线程通过suspend()方法暂停,则会出现以上值不同步的现象。
线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
线程优先级的一些特性:
- 继承性:在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
- 规则性:两个优先级不同的线程同时开始处理某些任务时了,高优先级的线程总是大部分先执行完。
- 随机性:随机性是说,优先级较高的线程不一定每一次都会被先执行完。
守护线程
在Java线程中有两种线程,一种是用户线程,另一种是守护线程。
守护线程是一种特殊的线程,它的特性有”陪伴“的含义,当进程中不存在非守护线程了,则守护线程自动销毁。
守护线程最典型的应用就是GC(垃圾回收器)