区分多线程与进程:
- 进程是受OS管理的基本运行单元,也就是“一次程序的执行”;
- 多线程则可以理解成在进程中独立运行的子任务,他们可以同时运行。
使用多线程的优点:
直观地,比如Windows(多任务操作系统)可以最大限度地使用CPU的空闲时间来处理其他任务,CPU在不同的任务之间不停地切换,由于切换的速度很快,我们就觉得这些任务好像是在同时运行。So,使用多线程可以在同一时间内运行更多不同类型的任务。
有一点需要注意的是:
线程被调用的顺序是随机的,与代码顺序无关。
实现多线程编程的方式:
- 继承Thread类
- 实现Runnable接口
public class Thread implements Runnable
Thread类实现了Runnable接口,他们之间具有多态关系,其实,继承Thread类创建新线程时,有不支持多继承的局限(因为Java语言的特点是单根继承),所以为了支持多继承,可以实现Runnable接口,但两种方法没有本质的区别。
Method 1 继承Thread类:
package com.multithread.www;
public class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("MyThread");
}
}
运行类代码:
package test;
import com.multithread.www.MyThread;
public class Run {
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
System.out.println("Finished!");
}
}
运行结果:
运行结果显示:run方法执行较晚,也证实了前面所说的线程被调用的顺序是随机的,与代码顺序无关
接下来展现线程具有的随机性,
package mythread;
public class MyThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
//super.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;
import mythread.MyThread;
public class Test {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.setName("mythread");
thread.start(); // 通知“线程规划器”此线程已准备就绪,等待调用此线程的run方法
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();
}
}
}
运行结果:
多次执行结果不同,因为CPU以不确定的方式(随机的时间)调用线程中的run方法,与start()方法顺序无关。
Method 2 实现Runnable接口:
如果要创建的线程已经有一个父类了,由于Java不支持多继承,就不能继承Thread类了,就需要implements Runnable接口创建新的线程。
package myrunnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("运行中...");
}
}
package test;
import myrunnable.MyRunnable;
public class Run {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable); // Thread类的带参数构造函数,传递Runnanle接口
thread.start();
System.out.println("运行结束!");
}
}
运行结果:
细想一下,Thread类实现了Runnable接口,意味着,Thread的构造函数Thread(Runnable target)不仅可以传入Runnable接口的对象,还可以传入一个Thread类的对象,从而实现了将一个Thread对象中的run方法交给其他线程进行调用。
线程间数据共享
自定义线程类中的实例变量针对其他线程有共享与不共享之分。
1. 不共享数据
package multithread_t3;
public class MyThread extends Thread {
private int count = 5; // 不共享变量
public MyThread(String name) {
super();
this.setName(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while(count > 0) {
count--;
System.out.println("由" + this.currentThread().getName() + "计算,count=" + count);
}
}
}
package test;
import multithread_t3.MyThread;
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();
}
}
运行结果:
三个线程有各自的count变量,各自减少count变量的值。
2. 共享数据
如果想多个线程共同完成count减少的操作,应如何?
多个线程应共享同一个变量count!
package t4;
public class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
count--;
System.out.println("由" + this.currentThread().getName() + "计算,count=" + count);
}
}
package test;
import t4.MyThread;
public class Run {
public static void main(String[] args) {
MyThread mythread = new MyThread();
Thread a = new Thread(mythread, "A"); // Thread类的构造函数Thread(ThreadGroup group, String name)
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();
}
}
运行结果:
完成了五个线程共同减少count变量的值,但线程A和B打印的count值都是3,这就说明A和B同时对count进行了操作,“非线程安全”也由此产生。
非线程安全:
非线程安全主要是指多个线程对同一对象中的同一个实例变量进行操作时会出现‘值被改变,值不同步’的情况,进而影响程序的执行流程
分析原因:
在某些JVM中,i–操作分为三步:
- 获取i值
- 计算i-1
- 对i赋值
所以,在i–过程中,如果多个线程同时访问i,一定会出现非线程安全问题。
修改代码:
package t4;
public class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run() { // 实现线程同步
// TODO Auto-generated method stub
super.run();
count--;
System.out.println("由" + this.currentThread().getName() + "计算,count=" + count);
}
}
运行结果:
在run()前加入synchronized关键字,实现了多线程同步,也就是按顺序排队的方式对count减1。
synchronized关键字可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。
最后,实现一下非线程安全的环境,
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{
@Override
public void run() {
LoginServlet.doPost("a", "aa");
}
}
package extthread;
import controller.LoginServlet;
public class BLogin extends Thread{
@Override
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();
}
}
运行结果:
加入synchronized关键字后,运行结果:
synchronized public static void doPost(String username, String password)
Reference:
高洪岩,《Java多线程编程核心技术》