多线程编程技术

一,多线程基础及概念

1. 线程和进程

可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。
线程可以理解成是在进程中独立运行的子任务。

单任务特点是排队执行,也就是同步。缺点是cpu利用率大幅降低。

一个进程在运行时至少会有一个线程在运行。

2. 线程实现的两种方式

一种是继承Thread类,另一种是实现Runnable接口。他们之间具有多态关系。使用继承Thread时,最大的局限是不支持多继承,为了支持多继承,可以实现Runnable接口。

2.1 继承Thread类

public class MyThread extends Thread{
    @override
    public void run(){
        super.run();
        System.out.println("myThread");
    }
}

运行代码如下:

public class Run{
    public static void main(String[] args){
        MyThread mythread = new MyThread();
        mythread.start();
        System.out.println("运行结束");
    }
}

代码执行顺序和运行顺序是无关的。

2.2 实现Runnable接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("运行中");
    }
}

运行代码:

public class Main {
    public static void main(String[] args) throws InterruptedException {
       Runnable runnable = new MyRunnable();
       Thread thread  = new Thread(runnable);
       thread.start();
        System.out.println("运行结束");
    }
}

3. 实例变量与线程安全

线程针对变量有共享和不共享之分,这在交互时是很重要的一个技术点。

3.1 不共享数据的情况

代码如下
public class MyThread extends Thread {
    private int count=5;
    public MyThread(String name){
        super();
        this.setName(name);
    }
    @Override
    public void run() {
        super.run();
        while (count>0){
            count--;
            System.out.println("由"+this.currentThread().getName()+" 计算,count="+count);
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
       MyThread a = new MyThread("a");
       MyThread b = new MyThread("b");
       MyThread c = new MyThread("c");
       a.start();
       b.start();
       c.start();
    }
}
运行效果如下:
由c 计算,count=4
由c 计算,count=3
由c 计算,count=2
由c 计算,count=1
由c 计算,count=0
由b 计算,count=4
由b 计算,count=3
由b 计算,count=2
由b 计算,count=1
由b 计算,count=0
由a 计算,count=4
由a 计算,count=3
由a 计算,count=2
由a 计算,count=1
由a 计算,count=0

这种情况是变量不共享

3.2 共享数据的情况

多个线程可以访问同一个变量

代码如下
public class MyThread extends Thread {
    private int count = 5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + " 计算,count=" + count);
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
       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();
    }
}
运行效果如下:
由E 计算,count=2
由A 计算,count=4
由C 计算,count=3
由D 计算,count=0
由B 计算,count=0

可以看出对A,B同时对count进行处理。产生了非线程安全问题。这不是我们想要的结果。

要解决些问题,只需要要在run方法前加入synchronized关键字,使多线程在执行run 方法时,以排队的方式进行处理。

public class MyThread extends Thread {
    private int count = 5;
    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + " 计算,count=" + count);
    }
}

synchronized说明对此该当上了锁,上了锁,当有线程正在运行此方法时,必须等待其他线程对run线程调用结束后才可执行。

synchronized可以在任意对象及方法上加锁,而加锁这段代码称为 互斥区 或者 临界区

非线程安全: 主要是指多个线程对同一个对象中的同一个变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。

如下是一个非线程安全的Demo,觉得不错:

public class LoginServlet {
    private static String username;
    private static String password;
    public static void post(String username1, String password1) {
        try {
            username = username1;
            if (username.equals("a")) {
                Thread.sleep(2000);
            }
            password = password1;
            System.out.println("username = " + username + "  password = " + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ALogin extends Thread{
    @Override
    public void run() {
        LoginServlet.post("a","aa");
    }
}

public class BLogin extends Thread {
    @Override
    public void run() {
        LoginServlet.post("b","bb");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ALogin aLogin = new ALogin();
        aLogin.start();
        BLogin bLogin = new BLogin();
        bLogin.start();
    }
}
运行结果:
username = b  password = bb
username = b  password = aa

解决这个非线程安全的问题也是使用synchronized

synchronized public class LoginServlet {
    private static String username;
    private static String password;
    public static void post(String username1, String password1) {
        try {
            username = username1;
            if (username.equals("a")) {
                Thread.sleep(2000);
            }
            password = password1;
            System.out.println("username = " + username + "  password = " + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4 线程常用方法

currentThread()方法

方法返回代码段正常被哪个线程调用的信息。

demo如下

public class CountOperate extends Thread {
    public CountOperate(){
        System.out.println("begin");
        System.out.println(Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("end");
    }

    @Override
    public void run() {
        System.out.println("be");
        System.out.println(Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("en");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountOperate countOperate = new CountOperate();
        Thread thread = new Thread(countOperate);
        thread.setName("A");
        thread.start();
    }
}
运行结果如下:
begin
main
Thread-0
end
be
A
Thread-0
en

结果说明:
构造方法CountOperate被main线程调用,
run方法是被名叫A的线程调用的

isAlive()方法

功能是判断当前的线程是否处于活跃状态。

活跃状态是指线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是存活的。

sleep()方法

作用是指在指定的毫秒数内让当前 正在执行的线程 休眠(暂停执行)。
这个 正在执行的线程是指this.currentThread()返回的线程

getId()方法

作用是取得线程的唯一标识。

yield()方法

使用是放弃当前的cpu资源,

5 停止线程

java 有三种方法能停止线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 停止线程可以使用Thread.stop()方法,方法不安全,且已废弃
  3. 可用Thread.interrupt()方法,方法不会终止正在运行的线程,还需要加入一个判断才能完成线程的停止

5.1 判断线程是否停止状态

判断线程状态是不是停止的。有两种方法

  1. this.interrupted(): 测试当前线程是否已经中断,执行后具有将状态标志清除为false的功能

  2. this.isInterrupted(): 测试线程是否已经中断,但不清除状态标志

    public class Mythread extends Thread {
    private long i =0;
    @Override
    public void run() {
    for (int j = 0; j < 5000; j++) {
    System.out.println(“j = “+j);
    }
    }
    }

    public class Main {
    public static void main(String[] args) {
    try {
    Mythread mythread = new Mythread();
    mythread.start();
    Thread.sleep(1000);
    mythread.interrupt();
    System.out.println(“是否停止1 = “+mythread.interrupted());
    System.out.println(“是否停止2 = “+mythread.interrupted());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    运行结果
    j = 4998
    j = 4999
    是否停止1 = false
    是否停止2 = false

    打印两个false,这个当前线程是main,从未中断过,所以打印的结果是两个 false.

如何使main 线程产生中断效果呢

public class MainDemo {
    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
        System.out.println("end");
    }
}
运行结果:
true
false
end

5.2 能停止的线程-异常法

public class Mythread extends Thread {
    @Override
    public void run() {
        try {
            for (int j = 0; j < 500000; j++) {
                if(this.interrupted()){
                    System.out.println("已经是停止状态了,我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("j = "+j);
            }
        } catch (InterruptedException e) {
            System.out.println("进入Mythered.java类run方法中的catch了");
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Mythread mythread = new Mythread();
            mythread.start();
            Thread.sleep(2000);
            mythread.interrupt();
        } catch (Exception e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

5.3 在沉睡中停止

5.4 stop()暴力停止

5.5 使用return停止线程

总结,还是建议使用 抛异常法来实现线程的停止.因为在catch块中还可以将异常向上抛.

6 暂停线程

暂停线程意味着还可以恢复运行,在java多线程中,可以 使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。

6.1 suspend与resume 方法的缺点-独占

在使用suspend 与resume 方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。

6.2 suspend与resume方法的缺点-不同步

在使用suspend与resume方法时也容易出现因为线程的暂停而导致数据不同步的情况

7 线程的优先级

线程可以划分优先级,优先级高的线程得到的CPU资源多,

线程划分为1-10这10个等级,如果小于1或大于10,则JDK抛出异常 throw new IllegalArgumentException() ,jdk使用3 个常量来预置定义优先级。

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

7.1 线程优先级具有继承特性

比如A线程启动线程B,则B线程的优先级和A是一样的。

代码如下:

public class Mythread1 extends Thread {
    @Override
    public void run() {
        System.out.println("Mythread1 run priority = "+this.getPriority());
        Mythread2 mythread2 = new Mythread2();
        mythread2.start();
    }
}

public class Mythread2 extends Thread {
    @Override
    public void run() {
        System.out.println("Mythread2 run priority="+this.getPriority());
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" thread begin = "+Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(7);
        System.out.println(Thread.currentThread().getName()+" thread end = "+Thread.currentThread().getPriority());
        Mythread1 mythread1 = new Mythread1();
        mythread1.start();
    }
}

运行结果:
main thread begin = 5
main thread end = 7
Mythread1 run priority = 7
Mythread2 run priority=7

7.2 优先级具有规则性

高优先级的线程就是大部分先执行完,但并不代表优先级高的线程全部先执行完。当线程优先级差别很大时,谁先报告完和代码的调用顺序无关。也就是说cpu尽量将执行资源让给优先级比较高的线程。

7.3 优先级具有随机性

线程的优先级高则优先执行完run()中的方法,但这个结果不能说的太肯定。因为线程优先级还具有随机性,也就是说优先级较高的线程不一定每次都先执行完。

不要把线程的优先级与运行结果的顺序作为衡量的标准。优先级高的线程并不一定每一次都先执行完run()中的方法,也就是说优先极与打印顺序无关。

8 守护线程

在java中有两种线程:

  • 用户线程
  • 守护线程 有陪伴的含义,当进程中不存在非守护线程了则守护线程自动销毁。比如垃圾守护线程。任何一个守护线程就是整个JVM中守护线程的保姆。只要非守护线程没有结束,守护线程就在工作。最典型的应用就是GC(垃圾回收器)

总结

本篇只是简单的介绍。后续陆续更新中……敬请期待