一、进程与线程
进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
线程是比进程还要小的运行单位,一个进程包含多少线程。
二、新建线程
方法一:继承Thread类,重写run(),run()方法代表线程要执行的任务。
public class TestMain extends Thread{
public TestMain() {
}
public TestMain(String name) {
super(name);
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(getName() + "正在执行第" + i +"次!");
}
}
public static void main(String[] args) {
TestMain testMain = new TestMain("线程1");
TestMain testMain2 = new TestMain("线程2");
testMain.start();
testMain2.start();
}
}
/* 运行结果:
线程2正在执行第1次!
线程1正在执行第1次!
线程1正在执行第2次!
线程2正在执行第2次!
线程1正在执行第3次!
线程2正在执行第3次!
线程2正在执行第4次!
线程2正在执行第5次!
线程1正在执行第4次!
线程1正在执行第5次!
*/
构造方法 | 说明 |
---|---|
Thread() | 创建一个线程对象 |
Thread(String name) | 创建一个具有指定名称的线程对象 |
Thread(Runnable target) | 创建一个基于Runnable接口实现类的线程对象 |
Thread(Runnable target, String name) | 创建一个基于Runnable接口实现类,并且具有指定名称的线程对象 |
常用方法 | 说明 |
---|---|
public void run() | 线程功能的实现 |
public void start() | 启动线程 |
public static void sleep(long m) | 线程休眠毫秒 |
public void join() | 优先执行调用join()方法的线程 |
方法二:实现Runnable接口重写run()方法。
public class TestMain2 implements Runnable {
private String name;
public TestMain2() {
}
public TestMain2(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(this.getName() + "该线程正在执行!");
}
}
public static void main(String[] args) {
TestMain2 testMain = new TestMain2("线程1");
TestMain2 testMain2 = new TestMain2("线程2");
Thread thread = new Thread(testMain);
Thread thread2 = new Thread(testMain2);
thread.start();
thread2.start();
}
}
/* 运行结果:
线程2该线程正在执行!
线程2该线程正在执行!
线程2该线程正在执行!
线程1该线程正在执行!
线程2该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程2该线程正在执行!
*/
为什么推荐实现Runnale接口?
- Java不支持多继承
- 不需要重写Thread类的其他方法
方法三:实现callable接口,重写call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出;使用start()方法来启动线程。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestMain3 implements Callable<String> {
private String name;
public TestMain3() {
}
public TestMain3(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Callable<String> callable = new TestMain3("线程1");
Callable<String> callable2 = new TestMain3("线程2");
FutureTask<String> futureTask = new FutureTask<>(callable);
FutureTask<String> futureTask2 = new FutureTask<>(callable2);
Thread thread = new Thread(futureTask);
Thread thread2 = new Thread(futureTask2);
// 启动线程
thread.start();
thread2.start();
// 获取call方法的返回值,先启动线程才可以获取到call的返值
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
futureTask2.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public String call() throws Exception {
for(int i=1; i<=5; i++) {
System.out.println(this.getName() + "该线程正在执行!");
}
return null;
}
}
/* 运行结果
线程2该线程正在执行!
线程2该线程正在执行!
线程2该线程正在执行!
线程2该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程2该线程正在执行!
*/
三、线程状态
- 新建(New)
- 进入可运行状态:调用start()方法
- 进行终止状态:调用stop()方法
- 可运行(Runnable)
- 进入正在运行状态:获取cpu使用权
- 进入终止状态:调用stop()方法
- 正在运行(Running)
- 进入可运行状态
- 时间片用完
- 调用yield()方法
- 进入阻塞状态
- 调用join()方法
- 调用wait()方法
- 调用sleep()方法
- 进行I/O请求
- 进入终止状态
- 终程执行完毕
- 异常终止
- 调用stop()方法
- 进入可运行状态
- 阻塞(Blocked)
- 进入可运行状态
- 等待调用join()的线程执行完毕
- 调用notify()或notifyAll()方法
- sleep超时
- I/O请求完成
- 进入终止状态:调用stop()方法
- 进入可运行状态
- 终止(Dead)
四、线程常用方法
方法:public static void sleep(long millis)
参数:休眠的时间,单位是毫秒
作用:在指定毫秒数内让正在执行的线程休眠(暂停执行)
public class TestMain2 implements Runnable {
private String name;
public TestMain2() {
}
public TestMain2(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(this.getName() + "该线程正在执行!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestMain2 testMain = new TestMain2("线程1");
TestMain2 testMain2 = new TestMain2("线程2");
Thread thread = new Thread(testMain);
Thread thread2 = new Thread(testMain2);
thread.start();
thread2.start();
}
}
方法:public final void join()
作用:等待调用该方法的线程结束后才能执行
public class TestJoin {
public void myTest() {
for(int i=1; i<5; i++) {
System.out.println("方法线程运行!");
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
// TestMain类请看新建线程方法一
TestMain testMain = new TestMain();
testMain.start();
try {
testMain.join(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
testJoin.myTest();
}
}
/*运行结果:
Thread-0正在执行第1次!
方法线程运行!
方法线程运行!
方法线程运行!
方法线程运行!
Thread-0正在执行第2次!
Thread-0正在执行第3次!
Thread-0正在执行第4次!
Thread-0正在执行第5次!
*/
五、线程优先级
Java为线程类提供了 10 个优先级,优先级可以用整数 1~10 表示,超过范围会抛出异常,主线程默认优先级为 5。
- MAX_PRIORITY:线程的最高优先级10
- :线程的最低优先级1
- NORM_PRIORITY:线程的默认优先级5
常用方法 | 说明 |
---|---|
public int getPriority() | 获取线程优先级的方法 |
public void setPriority(int newPriority) | 设置线程优先级的方法 |
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestMain3 implements Callable<String> {
private String name;
public TestMain3() {
}
public TestMain3(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Callable<String> callable = new TestMain3("线程1");
Callable<String> callable2 = new TestMain3("线程2");
FutureTask<String> futureTask = new FutureTask<>(callable);
FutureTask<String> futureTask2 = new FutureTask<>(callable2);
Thread thread = new Thread(futureTask);
Thread thread2 = new Thread(futureTask2);
// 修改优先级
thread.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
// 启动线程
thread.start();
thread2.start();
// 获取call方法的返回值,先启动线程才可以获取到call的返值
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
futureTask2.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程的优选级" + Thread.currentThread().getPriority());
}
@Override
public String call() throws Exception {
//String string = this.getName() + "正在运行";
for(int i=1; i<=5; i++) {
System.out.println(this.getName() + "该线程正在执行!");
}
return null;
}
}
/*运行结果
线程2该线程正在执行!
线程2该线程正在执行!
线程2该线程正在执行!
线程2该线程正在执行!
线程1该线程正在执行!
线程2该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
线程1该线程正在执行!
主线程的优选级5
*/
六、多线程运行问题
各个线程是通过竞争CPU时间而获得运行机会,各线程什么时候得到CPU时间,占用多久,是不可预测的,同样,一个正在运行着的线程在什么地方被暂停是不确定的。
public class Bank {
private String account;
private int money;
public Bank(String account, int money) {
this.setAccount(account);
this.setMoney(money);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
@Override
public String toString() {
return "Bank [帐户:" + getAccount() + ", 金钱:" + getMoney()+ "]";
}
public void save() {
int balance = getMoney();
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
balance += 1;
setMoney(balance);
System.out.println("存款+1");
}
public void load() {
int balance = getMoney();
balance -= 1;
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
setMoney(balance);
System.out.println("取款-1");
}
}
public class Save implements Runnable {
Bank bank;
public Save(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.save();
}
}
public class Load implements Runnable {
Bank bank;
public Load(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.load();
}
}
public class TestMain4 {
public static void main(String[] args) {
// 创建测试对象,给定金额10
Bank bank = new Bank("001", 10);
// 创建线程对象
Thread save = new Thread(new Save(bank));
Thread load = new Thread(new Load(bank));
// 开启线程
save.start();
load.start();
// 保证存取款的线程先执行
try {
save.join();
load.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("原金额:10," + bank);
}
}
/* 运行结果:
取款-1
存款+1
原金额:10,Bank [帐户:001, 金钱:10]
当将sleep(1000)方法反注释后,得到运行结果
取款-1
存款+1
原金额:10,Bank [帐户:001, 金钱:11]
*/
为什么会出现这种情况呢?其实,这就涉及到了一个同步的问题,我们可以假设,存款的线程可能执行到balance += 1 = 11
处就被暂停,转而先去执行取款线程,这时候因为存的钱还没同步到帐户上,此时的balance -= 1 = 9
,钱是算上来了,也同步去了,但是没有任意,当Bank
回到存款操作时,因为原先的balance += 1 = 11
己经运算完成,当同步到帐号时,你的金额就变成了11。
当然,这也只是其中的一种猜测而己,但是,从上面的猜测中我们可以得出一个结论:没有同步的线程很不可靠。
那么,如何解决这个问题呢?很简单,我们要对Bank对象进行锁定,保证在取款或存款的时候,不允许其它的线程对帐户余额进行操作。
如何实现呢?使用synchronized
关键字。
synchronized关键字用在:
- 成员方法
- 静态方法
- 语句块
比如说,我们给存取款方法加上synchronized关键字:
public synchronized void save() {
int balance = getMoney();
balance += 1;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(balance);
System.out.println("存款+1");
}
public synchronized void load() {
int balance = getMoney();
balance -= 1;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setMoney(balance);
System.out.println("取款-1");
}
/*
存款+1
取款-1
原金额:10,Bank [帐户:001, 金钱:10]
*/
嗯,结果正常了!
七、线程通信
常用方法 | 说明 |
---|---|
wait() | 中断方法的执行,使线程等待 |
notify() | 唤醒处于等待的某一个线程 |
notityAll() | 唤醒全部处于等待的线程 |
有两个线程,一个打印数字,从1-52,另一个打印字母,从A-Z,使用线程通信,按12A34B……5152Z的格式进行印。
public class TestMain {
public synchronized void printEng() {
int count = 0;
for (char c='A'; c<='Z'; c++) {
if (count == 1) {
try {
wait();
count = 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(c);
count += 1;
notifyAll();
}
}
public synchronized void printNum() {
int count = 0;
for (int i=1; i<=52; i++) {
if (count == 2) {
try {
wait();
count = 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(i);
count += 1;
notifyAll();
}
}
public static void main(String[] args) {
TestMain testMain = new TestMain();
Thread thread = new Thread(new Print1(testMain));
Thread thread2 = new Thread(new Print2(testMain));
thread.start();
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Print1 implements Runnable{
TestMain testMain;
public Print1(TestMain testMain) {
this.testMain = testMain;
}
@Override
public void run() {
testMain.printNum();
}
}
public class Print2 implements Runnable {
TestMain testMain;
public Print2(TestMain testMain) {
this.testMain = testMain;
}
@Override
public void run() {
testMain.printEng();
}
}
运行结果:
12A34B56C78D910E1112F1314G1516H1718I1920J2122K2324L2526M2728N2930O3132P3334Q3536R3738S3940T4142U4344V4546W4748X4950Y5152Z
首先,这个题目很明显,就是按照2个数字打印1个字母的顺序进行打印,那么,解法也就很明显了。
- 编写
printEng
和printNum
方法,实现打印字母和数字的功能。 - 实现
Runnable
接口创建两个类,分别为printEng
和printNum
方法实现多线程。 - 修改
printEng
和printNum
方法,实现同步,暂停和激活功能。