多线程编程
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.

京公网安备 11010502036488号