JAVA多线程
首先讲一下进程和线程的区别:
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中创建一个线程有两种方法:
①实现java.lang.Runnable接口,重写run()方法,启动:new Thread(this).start()。
1 package com.thread; 2 3 public class ThreadTest1 { 4 public static void main(String[] args) { 5 Runnable1 r = new Runnable1(); 6 //r.run();并不是线程开启,而是简单的方法调用 7 Thread t = new Thread(r);//创建线程 8 //t.run(); //如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 9 t.start(); //线程开启 10 for (int i = 0; i < 100; i++) { 11 System.out.println("main:"+i); 12 } 13 } 14 } 15 class Runnable1 implements Runnable{ 16 public void run() { 17 for (int i = 0; i < 100; i++) { 18 System.out.println("Thread-----:"+i); 19 } 20 } 21 }
要注意的是:
1.r.run()并不是启动线程,而是简单的方法调用。
2.Thread也有run()方法,如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3.并不是一启动线程(调用start()方法)就执行这个线程,而是进入就绪状态,什么时候运行要看CUP。
②继承java.lang.Thread类,重写run()方法。
1 package com.thread; 2 3 public class TestThread2 { 4 public static void main(String[] args) { 5 Thread1 t = new Thread1(); 6 //t.run(); //这里也不能直接调用方法 7 t.start(); 8 for (int i = 0; i < 100; i++) { 9 System.out.println("main:"+i); 10 } 11 } 12 } 13 14 //尽量使用实现Runnnable接口,因为接口比较灵活 15 class Thread1 extends Thread{ 16 @Override 17 public void run() { 18 for (int i = 0; i < 100; i++) { 19 System.out.println("Thread-----:"+i); 20 } 21 } 22 }
虽然两种方法都可行,但是最好还是用第一种方法,因为使用接口灵活性好,java中时单继承、多实现。
Thread类中常用的方法有:
①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
1 package com.thread; 2 import java.util.Date; 3 /** 4 * sleep()指在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。 5 * @author Administrator 6 * 7 */ 8 public class SleepTest { 9 public static void main(String[] args) { 10 Thread2 t = new Thread2(); 11 t.start(); 12 try { 13 Thread.sleep(10000); //主线程睡眠10秒钟 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 //主线程睡眠10秒钟后结束t线程 18 //t.interrupt(); //这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭 19 //t.stop();也是结束某个线程,这种方式比interrupt()更粗暴 20 t.flag = false; 21 } 22 } 23 class Thread2 extends Thread{ 24 boolean flag = true; //用这种方式结束线程很不错,用一个变量控制run方法什么时候不再执行,不会出现run方法没有执行完毕就结束 25 @Override 26 public void run() { //run方法一结束,整个线程就终止了 27 while(flag){ 28 System.out.println("---"+new Date()+"---"); 29 try { 30 sleep(1000); 31 } catch (InterruptedException e) { 32 return; 33 } 34 } 35 } 36 }
②join():指等待t线程终止。也可以理解为将t线程合并到当前线程来,等待t线程结束后再往下执行。相当于方法调用
1 package com.thread; 2 3 import java.util.Date; 4 5 /* 6 * t.join()方法指等待t线程终止。也可以理解为将t线程合并到当前线程来,等待t线程结束后再往下执行。相当于方法调用 7 */ 8 public class TestJoin { 9 public static void main(String[] args) { 10 Thread t = new Thread3("abc"); 11 t.start(); 12 for (int i = 0; i < 20; i++) { 13 System.out.println("我是main线程"); 14 if(i==10){ 15 try { 16 t.join(); 17 } catch (InterruptedException e1) { 18 // TODO Auto-generated catch block 19 e1.printStackTrace(); 20 } 21 } 22 try { 23 Thread.sleep(1000); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 } 29 } 30 class Thread3 extends Thread{ 31 public Thread3(String s) { //给该线程取一个名字,用getName()方法可以去到该名字 32 super(s); 33 } 34 @Override 35 public void run() { 36 for (int i = 0; i < 20; i++) { 37 System.out.println("我是"+getName()+"线程"); 38 try { 39 sleep(1000); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 } 44 } 45 }
运行该程序结果为:
1 我是main线程 2 我是abc线程 3 我是main线程 4 我是abc线程 5 我是main线程 6 我是abc线程 7 我是main线程 8 我是abc线程 9 我是main线程 10 我是abc线程 11 我是main线程 12 我是abc线程 13 我是main线程 14 我是abc线程 15 我是main线程 16 我是abc线程 17 我是main线程 18 我是abc线程 19 我是main线程 20 我是abc线程 21 我是main线程 22 我是abc线程 23 我是abc线程 24 我是abc线程 25 我是abc线程 26 我是abc线程 27 我是abc线程 28 我是abc线程 29 我是abc线程 30 我是abc线程 31 我是abc线程 32 我是main线程 33 我是main线程 34 我是main线程 35 我是main线程 36 我是main线程 37 我是main线程 38 我是main线程 39 我是main线程 40 我是main线程
可以看到从第22行起就开始顺序执行了,因为i=10的时候就将该形成合并了。
③yield():暂停当前正在执行的线程对象,并执行其他线程。
④setPriority(): 更改线程的优先级。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
1 package com.thread; 2 3 4 /*t.yield()暂停当前正在执行的线程对象,并执行其他线程。 5 * 6 * MIN_PRIORITY 1 7 * NORM_PRIORITY 5 8 * MAX_PRIORITY 10 9 */ 10 public class TestYield { 11 public static void main(String[] args) { 12 Thread4 t1 = new Thread4("t1"); 13 Thread4 t2 = new Thread4("t2"); 14 t1.setPriority(Thread.MAX_PRIORITY); 15 t2.setPriority(Thread.MIN_PRIORITY); 16 System.out.println(t1.getPriority()); 17 System.out.println(t2.getPriority()); 18 t1.start(); 19 t2.start(); 20 21 } 22 } 23 class Thread4 extends Thread{ 24 public Thread4(String s) { 25 super(s); 26 } 27 @Override 28 public void run() { 29 for (int i = 0; i < 1000; i++) { 30 System.out.println("我是"+getName()+"线程"+i); 31 if(i%10 == 0){ 32 yield(); 33 } 34 } 35 } 36 }
由于运行结果太长就没有贴上来了,运行该程序,可以看到t1和t2两个进程每次当i为10的倍数时都会让给其他线程执行。
⑤interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭
要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。
⑥还有很多的方法就不一一列举了.........
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题。
同步机制可以使用synchronized关键字实现。
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。
下面通过一个例子来对synchronized关键字的用法进行解析。
1.是否使用synchronized关键字的不同
1 例子程序1 2 3 public class ThreadTest 4 { 5 public static void main(String[] args) 6 { 7 Example example = new Example(); 8 9 Thread t1 = new Thread1(example); 10 Thread t2 = new Thread1(example); 11 12 t1.start(); 13 t2.start(); 14 } 15 16 } 17 18 class Example 19 { 20 public synchronized void execute() 21 { 22 for (int i = 0; i < 10; ++i) 23 { 24 try 25 { 26 Thread.sleep(500); 27 } 28 catch (InterruptedException e) 29 { 30 e.printStackTrace(); 31 } 32 System.out.println("Hello: " + i); 33 } 34 } 35 36 } 37 38 class Thread1 extends Thread 39 { 40 private Example example; 41 42 public Thread1(Example example) 43 { 44 this.example = example; 45 } 46 47 @Override 48 public void run() 49 { 50 example.execute(); 51 } 52 53 }
是否在execute()方法前加上synchronized关键字,这个例子程序的执行结果会有很大的不同。
如果不加synchronized关键字,则两个线程同时执行execute()方法,输出是两组并发的。
如果加上synchronized关键字,则会先输出一组0到9,然后再输出下一组,说明两个线程是顺次执行的。
2.多个方法的多线程情况
将程序改动一下,Example类中再加入一个方法execute2()。
之后再写一个线程类Thread2,Thread2中的run()方法执行的是execute2()。Example类中的两个方法都是被synchronized关键字修饰的。
1 例子程序2 2 3 public class ThreadTest 4 { 5 public static void main(String[] args) 6 { 7 Example example = new Example(); 8 9 Thread t1 = new Thread1(example); 10 Thread t2 = new Thread2(example); 11 12 t1.start(); 13 t2.start(); 14 } 15 16 } 17 18 class Example 19 { 20 public synchronized void execute() 21 { 22 for (int i = 0; i < 20; ++i) 23 { 24 try 25 { 26 Thread.sleep((long) Math.random() * 1000); 27 } 28 catch (InterruptedException e) 29 { 30 e.printStackTrace(); 31 } 32 System.out.println("Hello: " + i); 33 } 34 } 35 36 public synchronized void execute2() 37 { 38 for (int i = 0; i < 20; ++i) 39 { 40 try 41 { 42 Thread.sleep((long) Math.random() * 1000); 43 } 44 catch (InterruptedException e) 45 { 46 e.printStackTrace(); 47 } 48 System.out.println("World: " + i); 49 } 50 } 51 52 } 53 54 class Thread1 extends Thread 55 { 56 private Example example; 57 58 public Thread1(Example example) 59 { 60 this.example = example; 61 } 62 63 @Override 64 public void run() 65 { 66 example.execute(); 67 } 68 69 } 70 71 class Thread2 extends Thread 72 { 73 private Example example; 74 75 public Thread2(Example example) 76 { 77 this.example = example; 78 } 79 80 @Override 81 public void run() 82 { 83 example.execute2(); 84 } 85 86 }
如果去掉synchronized关键字,则两个方法并发执行,并没有相互影响。
但是如例子程序中所写,即便是两个方法:
执行结果永远是执行完一个线程的输出再执行另一个线程的。
说明:
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
结论:
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当一个线程访问某个对象的synchronized方法时,将该对象上锁,其他任何线程都无法再去访问该对象的synchronized方法了(这里是指所有的同步方法,而不仅仅是同一个方法),直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的synchronized方法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制关系。
尝试在代码中构造第二个线程对象时传入一个新的Example对象,则两个线程的执行之间没有什么制约关系。
3.考虑静态的同步方法
当一个synchronized关键字修饰的方法同时又被static修饰,之前说过,非静态的同步方法会将对象上锁,但是静态方法不属于对象,而是属于类,它会将这个方法所在的类的Class对象上锁。
一个类不管生成多少个对象,它们所对应的是同一个Class对象。
1 例子程序3 2 3 public class ThreadTest 4 { 5 public static void main(String[] args) 6 { 7 Example example = new Example(); 8 9 Thread t1 = new Thread1(example); 10 11 // 此处即便传入不同的对象,静态方法同步仍然不允许多个线程同时执行 12 example = new Example(); 13 14 Thread t2 = new Thread2(example); 15 16 t1.start(); 17 t2.start(); 18 } 19 20 } 21 22 class Example 23 { 24 public synchronized static void execute() 25 { 26 for (int i = 0; i < 20; ++i) 27 { 28 try 29 { 30 Thread.sleep((long) Math.random() * 1000); 31 } 32 catch (InterruptedException e) 33 { 34 e.printStackTrace(); 35 } 36 System.out.println("Hello: " + i); 37 } 38 } 39 40 public synchronized static void execute2() 41 { 42 for (int i = 0; i < 20; ++i) 43 { 44 try 45 { 46 Thread.sleep((long) Math.random() * 1000); 47 } 48 catch (InterruptedException e) 49 { 50 e.printStackTrace(); 51 } 52 System.out.println("World: " + i); 53 } 54 } 55 56 } 57 58 class Thread1 extends Thread 59 { 60 private Example example; 61 62 public Thread1(Example example) 63 64 { 65 this.example = example; 66 } 67 68 @Override 69 public void run() 70 { 71 Example.execute(); 72 } 73 74 } 75 76 class Thread2 extends Thread 77 { 78 private Example example; 79 80 public Thread2(Example example) 81 { 82 this.example = example; 83 } 84 85 @Override 86 public void run() 87 { 88 Example.execute2(); 89 } 90 91 }
所以如果是静态方法的情况(execute()和execute2()都加上static关键字),即便是向两个线程传入不同的Example对象,这两个线程仍然是互相制约的,必须先执行完一个,再执行下一个。
结论:
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的类所对应的Class对象。Java中,无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始。
4. synchronized块
synchronized块写法:
synchronized(object)
{
}
表示线程在执行的时候会将object对象上锁。(注意这个对象可以是任意类的对象,也可以使用this关键字)。
这样就可以自行规定上锁对象。
1 例子程序4 2 3 public class ThreadTest 4 { 5 public static void main(String[] args) 6 { 7 Example example = new Example(); 8 9 Thread t1 = new Thread1(example); 10 Thread t2 = new Thread2(example); 11 12 t1.start(); 13 t2.start(); 14 } 15 16 } 17 18 class Example 19 { 20 private Object object = new Object(); 21 22 public void execute() 23 { 24 synchronized (object) 25 { 26 for (int i = 0; i < 20; ++i) 27 { 28 try 29 { 30 Thread.sleep((long) Math.random() * 1000); 31 } 32 catch (InterruptedException e) 33 { 34 e.printStackTrace(); 35 } 36 System.out.println("Hello: " + i); 37 } 38 39 } 40 41 } 42 43 public void execute2() 44 { 45 synchronized (object) 46 { 47 for (int i = 0; i < 20; ++i) 48 { 49 try 50 { 51 Thread.sleep((long) Math.random() * 1000); 52 } 53 catch (InterruptedException e) 54 { 55 e.printStackTrace(); 56 } 57 System.out.println("World: " + i); 58 } 59 60 } 61 62 } 63 64 } 65 66 class Thread1 extends Thread 67 { 68 private Example example; 69 70 public Thread1(Example example) 71 { 72 this.example = example; 73 } 74 75 @Override 76 public void run() 77 { 78 example.execute(); 79 } 80 81 } 82 83 class Thread2 extends Thread 84 { 85 private Example example; 86 87 public Thread2(Example example) 88 { 89 this.example = example; 90 } 91 92 @Override 93 public void run() 94 { 95 example.execute2(); 96 } 97 98 }
例子程序4所达到的效果和例子程序2的效果一样,都是使得两个线程的执行顺序进行,而不是并发进行,当一个线程执行时,将object对象锁住,另一个线程就不能执行对应的块。
synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。当然,如果是静态方法,需要锁定的则是class对象。
可能一个方法中只有几行代码会涉及到线程同步问题,所以synchronized块比synchronized方法更加细粒度地控制了多个线程的访问,只有synchronized块中的内容不能同时被多个线程所访问,方法中的其他语句仍然可以同时被多个线程所访问(包括synchronized块之前的和之后的)。
注意:被synchronized保护的数据应该是私有的。
结论:
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;
synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的其他代码是可以被多个线程同时访问到的。

浙公网安备 33010602011771号