多线程编程

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.