[多线程]Java多线程01_初入门

1. 线程与进程的区别

进程 > 线程

进程:相当于启动一个软件,就启动了一个进程。当启动了QQ以及微信,那么这两个不同软件运行的独立任务就是两个不同的进程

线程:一个软件了里面,为了进行多个任务,就需要多个线程来协同工作,以可以尽可能多的使用计算机的资源。

注:由于CPU在线程、进程之间切换非常快,所以给人一种这些进程、线程都是在一起工作的错觉。

2. Java中的线程使用

Java是一门多线程的语言,而且,不论在安卓开发也好、服务器开发也好、桌面开发也好,都很多的使用了线程来完成多个任务。

安卓开发:使用多个线程,来执行多个任务,比如页面的同时渲染,在发送数据的时候确保Socket连接等等

服务器开发:最常见的SpringMVC开发,其实整个服务器只实例化一个DispatcherServlet,而能够支持多个用户同时在线操作的原因就是因为,SpringMVC使用了线程来对数据进行很好的隔离,从而使数据都是安全的。

桌面开发:跟安卓差不多。

需要说明的是,我们知道,Java程序的入口是main方法,而main方法本身就是一个线程,具体可以通过在main方法中调用currentThread()获取相对应的线程然后再通过getName()方法获取对应的名字,可以在控制台打印出来得到main的字符串

Java中使用多线程也是非常方便的,有两种情况,一种是继承Thread类,另外一种就是实现Runnable接口

1)继承Thread类

  • 编写线程类
package p01.threadClass;

/**
 * 通过继承Thread方式开发线程
 */
public class ExtendThread extends Thread {

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

  • 启动
public static void startExtThread() {
    ExtendThread extendThread1 = new ExtendThread();
    ExtendThread extendThread2 = new ExtendThread();
    ExtendThread extendThread3 = new ExtendThread();

    extendThread1.setName("Thread-01");
    extendThread2.setName("Thread-02");
    extendThread3.setName("Thread-03");

    extendThread1.start();
    extendThread2.start();
    extendThread3.start();
}
  • 打印:
Thread-01
Thread-03
Thread-02

这样就可以编写自己的线程任务了。

需要注意的是:
1. 虽然我们在main方法中按顺序调用三个线程,但并不意味着这三个线程会按照顺序运行,而是通过抢夺资源,哪个线程抢到了,就先运行,所以控制台打印的顺序也没有按照启动的顺序来;
2. 因为Java是单继承的关系,所以,并不推荐使用继承的方式来开发,而是通过下面实现接口的方式来做。

2)实现Runnable接口(推荐使用)

  • 编写线程类
package p01.threadClass;

/**
 * 实现Runable
 */
public class ImplementThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

  • 启动
public static void startImplThread() {
    /** 需要通过传入我们的实现的线程的对象来创建线程 */
    Thread thread1 = new Thread(new ImplementThread());
    Thread thread2 = new Thread(new ImplementThread());
    Thread thread3 = new Thread(new ImplementThread());

    thread1.setName("Thread-01");
    thread2.setName("Thread-02");
    thread3.setName("Thread-03");

    thread1.start();
    thread2.start();
    thread3.start();
}
  • 控制台
Thread-01
Thread-02
Thread-03

效果和继承是一样的,只不过通过实现,在Java开发思想中更加符合,也能规避单继承的缺陷。

3. 初识线程安全

出现线程安全问题,我们需要让多个线程共享同一个变量,当多个线程读取变量的时候,如果没有对该变量做任何限制,则会出现线程安全的问题。

  • 线程类
public class ThreadSafeDemo extends Thread {

    private int n = 5;

    @Override
    public void run() {
        n--;
        System.out.println("由" + this.currentThread().getName() + "执行,count = " + n);
    }   
}
  • 启动类
public static void startThreadSafeDemo() {
    ThreadSafeDemo demo = new ThreadSafeDemo();
    Thread th1 = new Thread(demo, "A");
    Thread th2 = new Thread(demo, "B");
    Thread th3 = new Thread(demo, "C");
    Thread th4 = new Thread(demo, "D");
    Thread th5 = new Thread(demo, "E");

    th1.start();
    th2.start();
    th3.start();
    th4.start();
    th5.start();
}
  • 结果
由B执行,count = 2
由D执行,count = 1
由A执行,count = 2
由C执行,count = 2
由E执行,count = 0

结果得到的是一个全部乱套了的数据.

原因:i--不是一个原始性操作,在jvm内部,i--分为三步运行:

1. 取得原有i的值
2. 计算i-1
3. 对i进行赋值

那么在这个过程中,可能线程A在执行第一步和第二步的时候,CPU资源就被线程B抢走了,导致A对i进行了减一操作,但是还没把值赋回去,而线程B获取的值是还没减一操作的值,所以这个过程导致了线程安全的问题。

1)初步解决方法

run方法加上synchronized关键字,可以解决,但是速度较慢,后续会慢慢根据这个思路进行优化。

  • 线程类代码片段
@Override
public synchronized void run() {
    n--;
    System.out.println("由" + this.currentThread().getName() + "执行,count = " + n);
}
  • 结果
由A执行,count = 4
由B执行,count = 3
由C执行,count = 2
由D执行,count = 1
由E执行,count = 0

原因:加上synchronized关键字,可以保证一个线程在该方法内部进行计算的时候,不能被其他线程抢走资源,也就是说,当线程A在执行i--上面那三个操作的时候,只能等到线程A执行完成,才能被其他线程获取。

4. 总结

到这里就已经把Java开发线程基本步骤给学完了,接下来就是要管理线程的生命周期。例如启动、暂停、停止等等,还有一系列的线程安全问题。