多线程编程
Thread类关键技术点:
线程的启动
如何使线程暂停
如何使线程停止
线程的优先级
线程安全相关的问题
1.main线程和main方法没有任何关系,仅仅是名字相同;
2.进程的概念,联想任务管理器中的进程,就是一个个exe应用程序;
3.什么是多线程?就是边吃饭边放屁,边听音乐边打字;
4.什么是同步?就是单纯的排队,一个执行完再执行下一个;
5.什么是异步?就是快速地轮流执行,联系硬件中的流水灯,处理器的来回执行速度远远大于人们的反应速度。异步就是多线程;
6.多线程的实现。两种方法,一种是继承Thread类,另一种是实现Runnable接口。其实Thread类也是实现了Runnable接口。区别:类只能单根继承,接口可以实现多继承;
7.使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。下面举例:
代码:
package test;
public class MyThread extends Thread{
public void run(){
super.run();
System.out.println("MyThread");
}
}
代码:
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
mythread.start();
System.out.println("运行结束!");
}
}
结果截图:
如图,MyThread.java类中的run方法执行的时间比较晚。
8.线程的随机性
代码:
package test;
public class MyThread extends Thread{
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();
}
}
}
代码:
package test;
public class Test {
public static void main( String[] args ){
try{
MyThread thread = new MyThread();
thread.setName("myThread");
thread.start();
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();
}
}
}
运行结果:
总结,线程具有随机的特性。start方法通知“线程规划器”线程已经准备就绪,等待调用线程对象的run方法。启动线程具有异步执行的效果。
9.执行start方法的顺序不代表线程启动的顺序,举例:
代码:
package test;
public class MyThread extends Thread{
private int i;
public MyThread(int i){
super();
this.i = i;
}
public void run(){
System.out.println(i);
}
}
代码:
package test;
public class Test{
public static void main( String[] args ){
MyThread t11 = new MyThread(1);
MyThread t12 = new MyThread(2);
MyThread t13 = new MyThread(3);
MyThread t14 = new MyThread(4);
MyThread t15 = new MyThread(5);
MyThread t16 = new MyThread(6);
MyThread t17 = new MyThread(7);
MyThread t18 = new MyThread(8);
MyThread t19 = new MyThread(9);
MyThread t20 = new MyThread(10);
t11.start();
t12.start();
t13.start();
t14.start();
t15.start();
t16.start();
t17.start();
t18.start();
t19.start();
t20.start();
}
}
结果:
10.实现Runnable接口,如果欲创建的线程类已经有一个父类了,这时就不能再继承自Thread类,因为不支持多继承,所以就需要实现Runnable接口来应对这样的情况。
举例:
构造函数支持传入一个Runnable接口的对象。
代码:
package myrunnable;
public class MyRunnable implements Runnable {
public void run() {
System.out.println("运行中!");
}
}
package test;
import myrunnable.MyRunnable;
public class Test{
public static void main(String[] args){
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}
而且,因为Thread.java类也实现了Runnable接口,所以,构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run方法交由其他的线程进行调用。
11.实例变量和线程安全
数据不共享的情况:
代码:
package test;
public class MyThread extends Thread{
private int count = 5;
public MyThread(String name){
super();
this.setName(name);
}
public void run(){
super.run();
while(count>0){
count--;
System.out.println( "由 "+ this.currentThread().getName() +
" 计算,count="+count );
}
}
}
package test;
public class Run {
public static void main(String[] args){
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
结果:
可以看到,一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享。
12.变量共享、共享数据的情况:
共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数。
代码:
package test;
public class MyThread extends Thread{
private int count =5 ;
public void run(){
super.run();
count--;
//此示例不要用for语句,因为使用同步后其他线程就得不到运行的机会了
//一直由一个线程进行减法运算
System.out.println( "由 "+this.currentThread().getName()
+" 计算,count=" +count );
}
}
package test;
public class Run {
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();
}
}
运行结果:
从结果可以看出,线程A和线程B打印出的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全” 问题:我们想要的打印结果不是重复的,是依次递减的。
比如在某些JVM中,i–的操作分成了3步:
1.取得原有i值;
2.计算i-1;
3.对i进行赋值。
如果在进行3个步骤的过程中,有多个线程同时访问,那么一定会出现非线程安全问题。
解决方法是在MyThread类中的run方法用synchronized修饰符进行修饰。
通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他的线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果。
synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。
当一个线程想要执行同步方法里面的代码是,线程首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronized里面的代码,如果不能拿到这把锁,那么这个线程就会不断尝试拿这把锁,直到能够拿到为止,而且是多个线程同时去争抢这把锁。
13.非线程安全问题举例
代码:
package controller;
//本类模拟成一个Servlet组件
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username,String password){
try {
usernameRef = username;
if ( username.equals("a")){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println( "username= "+usernameRef +" password="+passwordRef );
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
package extthread;
import controller.LoginServlet;
public class ALogin extends Thread{
public void run(){
LoginServlet.doPost("a","aa" );
}
}
package extthread;
import controller.LoginServlet;
public class BLogin extends Thread {
public void run(){
LoginServlet.doPost("b","bb");
}
}
package test;
import extthread.ALogin;
import extthread.BLogin;
public class Run {
public static void main(String[] args){
ALogin a= new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
运行结果:
14.留意i–和System.out.println()的异常
package test;
public class MyThread extends Thread{
private int i =5;
public void run(){
System.out.println( "i="+(i--) + " threadName="
+Thread.currentThread().getName());
}
}
package test;
import extthread.ALogin;
import extthread.BLogin;
public class Run {
public static void main(String[] args){
MyThread run = new MyThread();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
Thread t5 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
上面的实验说明:虽然println方法在内部是同步的,但是i–的操作却是在进入println方法之前发生的,所以有发生非线程安全问题的概率。
所以,为了防止发生非线程安全问题,还是应继续使用同步方法。
public void println(String x){
synchronized(this){
print(x);
newLine();
}
}
15.currentThread方法
currentThread方法可以返回代码段正在被哪个线程调用的信息。
举例:
package test;
public class Run {
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
}
}
运行结果:
结果说明,main方法被名为main的线程调用。
package test;
public class MyThread extends Thread{
public MyThread(){
System.out.println("构造方法的打印: "+Thread.currentThread().getName() );
}
public void run(){
System.out.println( "run方法的打印: "+Thread.currentThread().getName() );
}
}
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
mythread.start();
//mythread.run();
}
}
运行结果:
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
//mythread.start();
mythread.run();
}
}
16.isAlive方法
isAlive方法的功能是判断当前的线程是否处于活动状态。
package test;
public class MyThread extends Thread{
public void run(){
System.out.println( "run=: "+ this.isAlive() );
}
}
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
System.out.println( "begin ==" + mythread.isAlive() );
mythread.start();
System.out.println( "end ==" + mythread.isAlive());
}
}
运行结果:
方法isAlive的作用是测试线程是否处于活动状态?
什么是活动状态呢?
活动状态就是线程已经启动 且尚未终止。线程处于正在运行或真被开始运行的状态,就认为线程是“存活”的。
在使用isAlive方法时,如果将线程对象以构造参数的方式传递给Thread对象进行start启动时,运行的结果和前面的实例是有差异的。
造成差异的原因是来自于Thread.currentThread()和this的差异。
17.sleep方法
方法sleep的作用是在指定的毫秒数内让当前的“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
package test;
public class MyThread extends Thread{
public void run(){
try {
System.out.println("run threadName ="
+this.currentThread().getName() + " begin");
Thread.sleep(2000);
System.out.println( "run threadName ="
+this.currentThread().getName()+" end");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
System.out.println( "begin ==" + System.currentTimeMillis() );
mythread.run();
System.out.println( "end ==" + System.currentTimeMillis());
}
}
运行结果:
而如果将mythread中的run方法改为调用start方法:
代码:
package test;
public class Run {
public static void main(String[] args){
MyThread mythread = new MyThread();
System.out.println( "begin ==" + System.currentTimeMillis() );
mythread.start();
System.out.println( "end ==" + System.currentTimeMillis());
}
}
运行结果:
第二版本中,因为main线程和mythread线程是异步执行的,所以首先打印的信息是begin和end.
18.getId方法
package test;
public class Test{
public static void main(String[] args){
Thread runThread = Thread.currentThread();
System.out.println(
runThread.getName() + " "
+runThread.getId()
);
}
}
19.停止线程
停止线程在java语言汇总并不像break语句那样干脆,需要一些技巧性的处理。
使用java内置支持多线程的类设计多线程应用是很常见的事情,然而,多线程给开发人员带来了一些新的挑战,如果处理不好就狐疑导致超出预期的行为并且难以定位错误。
本节应该实现更好地停止一个线程。停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前的操作。虽然这看起来非常简单,但是必须做好防范措施,以便达到预期的效果。停止一个线程可以使用Thread.stop()方法,但最好不用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被弃用作废的,在将来的java版本中,这个方法将不可用或不被支持。
大多数停止一个线程的操作使用Thread.interrupt()方法,尽管方法的名称是“停止,中止”的意思,但是这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
在java中有以下3种方法可以终止正在运行的线程:
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止;
2.使用stop方法强行终止线程,但是不推荐使用,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果;
3.使用interrupt方法中断线程。
20.停止不了的线程
interrupt方法的使用效果并不像for+break语句那样,马上就停止循环。调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
21.