什么是多线程
a、进程的概念:进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
b、线程是比进程还要小的运行单位,一个进程包含多个线程。
c、线程可以看做一个子程序。
Ps:之所以看起来好像它们是同时运行是因为时间片单位很小的时候,当它们属于轮流运行时,看起来就像“同时运行”。
线程的创建
- 创建一个Thread类,或者一个Thread子类的对象。
- 创建一个实现Runnable接口的类的对象。
Runnable 接口
- 只有一个方法run()。
- Runnable是Java中用以实现线程的接口。
- 任何实现线程功能的类都必须实现该接口。
A、通过继承Thread类的方式创建线程类,重写run()方法
- i、线程.start() 只能被启动一次,再次启动的话不会报CE,但会报RE。
- ii、启动线程不是 run() 方法。
- iii、线程运行的结果能顺序、交错执行都是有可能的。
class MyThread extends Thread{
public void run(){
System.out.println(getName()+"该线程正在执行!");
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程1");
MyThread mt=new MyThread();
mt.start();//启动线程
// mt.start();
System.out.println("主线程2");
}
}
Console:
主线程1
主线程2
Thread-0该线程正在执行!
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run(){
for(int i=1;i<=10;i++){
System.out.println(getName()+"正在运行"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread mt1=new MyThread("线程1");
MyThread mt2=new MyThread("线程2");
mt1.start();
mt2.start();
}
}
Console:
线程2正在运行1
线程1正在运行1
线程2正在运行2
线程1正在运行2
线程1正在运行3
线程1正在运行4
线程1正在运行5
线程1正在运行6
线程1正在运行7
线程1正在运行8
线程1正在运行9
线程1正在运行10
线程2正在运行3
线程2正在运行4
线程2正在运行5
线程2正在运行6
线程2正在运行7
线程2正在运行8
线程2正在运行9
线程2正在运行10
B、通过实现Runnable接口的方式创建(推荐)
优点:为什么要实现Runnable接口?
- Java不支持多继承。
- 不打算重写Thread类的其他方法。
class PrintRunnable implements Runnable {
@Override
public void run() {
int i = 1;
while (i <= 10)
System.out.println(Thread.currentThread().getName() + "正在运行" + (i++));
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr = new PrintRunnable();
Thread t1 = new Thread(pr);
t1.start();
PrintRunnable pr1 = new PrintRunnable();
Thread t2 = new Thread(pr1);
t2.start();
}
}
Console:
Thread-0正在运行1
Thread-1正在运行1
Thread-0正在运行2
Thread-1正在运行2
Thread-0正在运行3
Thread-1正在运行3
Thread-0正在运行4
Thread-1正在运行4
Thread-0正在运行5
Thread-1正在运行5
Thread-0正在运行6
Thread-1正在运行6
Thread-0正在运行7
Thread-1正在运行7
Thread-0正在运行8
Thread-1正在运行8
Thread-0正在运行9
Thread-1正在运行9
Thread-0正在运行10
Thread-1正在运行10
Ps:多线程分别处理不同任务。
class PrintRunnable implements Runnable {
@Override
public void run() {
int i = 1;
while (i <= 10)
System.out.println(Thread.currentThread().getName() + "正在运行" + (i++));
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr = new PrintRunnable();
Thread t1 = new Thread(pr);
t1.start();
Thread t2 = new Thread(pr);
t2.start();
}
}
Console:
Thread-0正在运行1
Thread-1正在运行1
Thread-0正在运行2
Thread-1正在运行2
Thread-1正在运行3
Thread-0正在运行3
Thread-1正在运行4
Thread-1正在运行5
Thread-0正在运行4
Thread-1正在运行6
Thread-1正在运行7
Thread-1正在运行8
Thread-1正在运行9
Thread-0正在运行5
Thread-1正在运行10
Thread-0正在运行6
Thread-0正在运行7
Thread-0正在运行8
Thread-0正在运行9
Thread-0正在运行10
Ps:多线程处理同个任务。
/* 模拟多线程共享资源 i */
class PrintRunnable implements Runnable {
int i = 1;
@Override
public void run() {
while (i <= 10)
System.out.println(Thread.currentThread().getName() + "正在运行" + (i++));
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr = new PrintRunnable();
Thread t1 = new Thread(pr);
t1.start();
Thread t2 = new Thread(pr);
t2.start();
}
}
Console:
Thread-0正在运行1
Thread-0正在运行3
Thread-1正在运行2
Thread-0正在运行4
Thread-1正在运行5
Thread-0正在运行6
Thread-1正在运行7
Thread-0正在运行8
Thread-1正在运行9
Thread-0正在运行10
Ps:多线程处理同个任务。
线程的状态
- 新建(New)
- 可运行(Runnable)
- 正在运行(Running)
- 阻塞(Blocked)
- 终止(Dead)
线程的生命周期
Ps:stop() 方法已过期,这里是为了理解方便,所以才如此标注。
线程的调度
- 暂停执行:阻塞状态。
- 因为sleep到运行状态中间还有一些步骤要处理,所以如果遇到非常需要精确时间的应用时,会误差一点时间。
- sleep应用:定时刷新数据。
class MyThread implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!");
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread t=new Thread(mt);
t.start();
Thread t1=new Thread(mt);
t1.start();
}
}
Console:
Thread-0执行第1次!
Thread-1执行第1次!
Thread-0执行第2次!
Thread-1执行第2次!
Thread-0执行第3次!
Thread-1执行第3次!
Thread-0执行第4次!
Thread-1执行第4次!
Thread-0执行第5次!
Thread-1执行第5次!
Thread-0执行第6次!
Thread-1执行第6次!
Thread-0执行第7次!
Thread-1执行第7次!
Thread-0执行第8次!
Thread-1执行第8次!
Thread-0执行第9次!
Thread-1执行第9次!
Thread-0执行第10次!
Thread-1执行第10次!
Ps:一般情况,会交错执行,因为当线程-0运行一次后,休眠,这时,线程-1有很大概率抢占;依此类推。
线程让步(yield方法)
由运行态到就绪态,停止一下后再由就绪态到运行态,暂停自己的线程,是一个静态的方法。
- 暂停当前正在执行的线程对象,并执行其他线程。
- 意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。
- 但是yield不能立刻交出CPU,会出现同一个线程一直执行的情况,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
- 注意调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
package Thread;
public class YieldDemo extends Thread{
public static void main(String[] args) throws InterruptedException {
YieldDemo demo = new YieldDemo();
Thread th = new Thread(demo);
th.start(); // 就绪
// CPU 调度就运行
for(int i = 0 ;i<10;i++) {
if(5==i) {
// 暂挺本线程 main
Thread.yield();
}
System.out.println("main......"+i);
}
}
@Override
public void run(){
for(int i = 0 ;i<10;i++) {
System.out.println("yield......"+i);
}
}
}
Ps:即调用 join() 方法的线程优先执行,使得其他线程一定会在他后面执行,因为 CPU 其实同一时刻只能执行一个任务,只是速度太快了,让我们人感觉起来是一起执行的。
Ps:跟上述的情况一样,只是多了个时间内限制,在这个时间段内能达到优先执行效果,过了这段时间,就会可能出现多线程之间的交错了。
class MyThread extends Thread{
public void run(){
for(int i=1;i<=75;i++)
System.out.println(getName()+"正在执行"+i+"次!");
}
}
public class JoinDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
mt.start();
try {
mt.join(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=1;i<=50;i++){
System.out.println("主线程运行第"+i+"次!");
}
System.out.println("主线程运行结束!");
}
}
Console:
Thread-0正在执行1次!
Thread-0正在执行2次!
Thread-0正在执行3次!
Thread-0正在执行4次!
Thread-0正在执行5次!
Thread-0正在执行6次!
Thread-0正在执行7次!
Thread-0正在执行8次!
Thread-0正在执行9次!
Thread-0正在执行10次!
Thread-0正在执行11次!
Thread-0正在执行12次!
Thread-0正在执行13次!
Thread-0正在执行14次!
Thread-0正在执行15次!
Thread-0正在执行16次!
Thread-0正在执行17次!
Thread-0正在执行18次!
Thread-0正在执行19次!
Thread-0正在执行20次!
Thread-0正在执行21次!
Thread-0正在执行22次!
Thread-0正在执行23次!
Thread-0正在执行24次!
Thread-0正在执行25次!
Thread-0正在执行26次!
Thread-0正在执行27次!
Thread-0正在执行28次!
Thread-0正在执行29次!
Thread-0正在执行30次!
Thread-0正在执行31次!
Thread-0正在执行32次!
Thread-0正在执行33次!
Thread-0正在执行34次!
Thread-0正在执行35次!
Thread-0正在执行36次!
Thread-0正在执行37次!
Thread-0正在执行38次!
Thread-0正在执行39次!
Thread-0正在执行40次!
Thread-0正在执行41次!
Thread-0正在执行42次!
Thread-0正在执行43次!
Thread-0正在执行44次!
Thread-0正在执行45次!
Thread-0正在执行46次!
Thread-0正在执行47次!
Thread-0正在执行48次!
Thread-0正在执行49次!
Thread-0正在执行50次!
Thread-0正在执行51次!
Thread-0正在执行52次!
Thread-0正在执行53次!
Thread-0正在执行54次!
Thread-0正在执行55次!
Thread-0正在执行56次!
Thread-0正在执行57次!
Thread-0正在执行58次!
主线程运行第1次!
Thread-0正在执行59次!
主线程运行第2次!
主线程运行第3次!
Thread-0正在执行60次!
主线程运行第4次!
Thread-0正在执行61次!
主线程运行第5次!
主线程运行第6次!
主线程运行第7次!
主线程运行第8次!
主线程运行第9次!
主线程运行第10次!
主线程运行第11次!
主线程运行第12次!
主线程运行第13次!
主线程运行第14次!
Thread-0正在执行62次!
主线程运行第15次!
主线程运行第16次!
主线程运行第17次!
主线程运行第18次!
主线程运行第19次!
主线程运行第20次!
主线程运行第21次!
主线程运行第22次!
主线程运行第23次!
主线程运行第24次!
主线程运行第25次!
主线程运行第26次!
Thread-0正在执行63次!
主线程运行第27次!
Thread-0正在执行64次!
主线程运行第28次!
Thread-0正在执行65次!
主线程运行第29次!
Thread-0正在执行66次!
主线程运行第30次!
Thread-0正在执行67次!
主线程运行第31次!
Thread-0正在执行68次!
主线程运行第32次!
主线程运行第33次!
主线程运行第34次!
主线程运行第35次!
主线程运行第36次!
主线程运行第37次!
主线程运行第38次!
主线程运行第39次!
主线程运行第40次!
主线程运行第41次!
主线程运行第42次!
主线程运行第43次!
主线程运行第44次!
主线程运行第45次!
主线程运行第46次!
主线程运行第47次!
Thread-0正在执行69次!
主线程运行第48次!
Thread-0正在执行70次!
主线程运行第49次!
Thread-0正在执行71次!
主线程运行第50次!
Thread-0正在执行72次!
主线程运行结束!
Thread-0正在执行73次!
Thread-0正在执行74次!
Thread-0正在执行75次!
附:加强对 join 理解
- 如果某个线程在另一个线程上调用 join(),此线程将被挂起(阻塞),直到调用的线程结束才恢复。
这句话的解读(见代码)
Ps:在 main 主线程中调用了其他线程的 join 方法,main 线程就堵塞,直到其他线程运行完了才运行。
package Thread;
public class JoinDemo01 extends Thread {
public static void main(String[] args) throws InterruptedException {
JoinDemo01 demo = new JoinDemo01();
Thread th = new Thread(demo);
th.start(); // 就绪
for(int i = 0 ;i<10;i++) { // CPU 调度就运行
if(5==i) {
th.join(); // mian阻塞
}
System.out.println("main......"+i);
}
}
@Override
public void run() {
for(int i = 0 ;i<10;i++) {
System.out.println("join......"+i);
}
}
}
线程的优先级
- Java为线程类提供了10个优先级。
- 优先级可以用整数1-10表示,超过范围会抛出异常。
- 主线程默认优先级为5。
- 不一定优先级高的线程优先执行,具体看环境:操作系统的规则 + CPU运行方式。
- Java中的线程优先级具有继承的特性,比如线程1启动线程2,那么线程2的优先级就和线程1的优先级是一样的。
- 线程的优先级只能确保CPU尽量将执行的资源让给优先级高的线程用,但不保证定义的高优先级的线程的大部分都能先于低优先级的线程执行完。
优先级常量
- MAXPRIORITY:线程的最高优先级10
- MIN_PRIORITY:线程的最低优先级1
- NORM_PRIORITY:线程的默认优先级5
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=50;i++){
System.out.println("线程"+name+"正在运行"+i);
}
}
}
public class PriorityDemo {
public static void main(String[] args) {
//获取主线程的优先级
int mainPriority=Thread.currentThread().getPriority();
//System.out.println("主线程的优先级为:"+mainPriority);
MyThread mt1=new MyThread("线程1");
MyThread mt2=new MyThread("线程2");
//mt1.setPriority(10);
mt1.setPriority(Thread.MAX_PRIORITY);
mt2.setPriority(Thread.MIN_PRIORITY);
mt2.start();
mt1.start();
//System.out.println("线程1的优先级为:"+mt1.getPriority());
}
}
多线程运行问题
- 各个线程是通过竞争CPU时间而获得运行机会的。
- 各线程什么时候得到CPU时间,占用多久,是不可预测的。
- 一个正在运行着的线程在什么地方被暂停是不确定的。
举例:银行存取款经典问题
package com.imooc.bank;
public class Bank {
private String account;// 账号
private int balance;// 账户余额
public Bank(String account, int balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank [账号:" + account + ", 余额:" + balance + "]";
}
// 存款
public void saveAccount() {
// 获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额,存100元
balance += 100; // 1
// 修改账户余额
setBalance(balance); // 3
// 输出存款后的账户余额
System.out.println("存款后的账户余额为:" + balance);
}
public void drawAccount() {
// 在不同的位置处添加sleep方法
// 获得当前的帐户余额
int balance = getBalance();
// 修改余额,取200
balance = balance - 200; // 2
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
// 修改帐户余额
setBalance(balance); // 4
System.out.println("取款后的帐户余额:" + balance);
}
}
package com.imooc.bank;
//取款
public class DrawAccount implements Runnable{
Bank bank;
public DrawAccount(Bank bank){
this.bank=bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
package com.imooc.bank;
//存款
public class SaveAccount implements Runnable{
Bank bank;
public SaveAccount(Bank bank){
this.bank=bank;
}
public void run(){
bank.saveAccount();
}
}
package com.imooc.bank;
public class Test {
public static void main(String[] args) {
// 创建帐户,给定余额为1000
Bank bank=new Bank("1001",1000);
//创建线程对象
SaveAccount sa=new SaveAccount(bank);
DrawAccount da=new DrawAccount(bank);
Thread save=new Thread(sa);
Thread draw=new Thread(da);
save.start();
draw.start();
try {
// 不分先后顺序,取决于(同步 + 上面的start()顺序)
draw.join();
save.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bank);
}
}
Console:
Bank [账号:1001, 余额:1000]
存款后的账户余额为:1100
取款后的帐户余额:800
Ps:因为线程会交错执行的缘故,代码中标注的 1、2、3、4 这种情况也是有概率发生的,那这样一来,当存款 +100 时,还没来得及 set 进去,就开始执行取款,这时因为存款没来得及 set,所以这里的总金额还是 1000 对不对!好,问题就出现在这里,这时 -200 的话就变成 800,然后此时存款线程现在开始执行 set,变成 1100 没错对吧,然后取款线程这时也开始执行 set,那么就变成 800 了最终结果!悲剧~
线程的同步(互斥)
- 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
- 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
// 修改后的代码
package com.imooc.bank;
public class Bank {
private String account;// 账号
private int balance;// 账户余额
public Bank(String account, int balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank [账号:" + account + ", 余额:" + balance + "]";
}
// 存款
public synchronized void saveAccount() {
// 获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额,存100元
balance += 100;
// 修改账户余额
setBalance(balance);
// 输出存款后的账户余额
System.out.println("存款后的账户余额为:" + balance);
}
public void drawAccount() {
synchronized (this) {
// 在不同的位置处添加sleep方法
// 获得当前的帐户余额
int balance = getBalance();
// 修改余额,取200
balance = balance - 200;
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
// 修改帐户余额
setBalance(balance);
System.out.println("取款后的帐户余额:" + balance);
}
}
}
Console:
Bank [账号:1001, 余额:1000]
存款后的账户余额为:1100
取款后的帐户余额:900Ps:正确!!!
线程间的通信
- wait)方法:中断方法的执行,使线程等待。
- notify()方法:唤醒处于等待的某一个线程,使其结束等待。
- nofifyAll()方法:唤醒所有处于等待的线程,使它们结束等待。
举例:生产一个,消费一个,依次循环
package com.imooc.queue;
public class Consumer implements Runnable{
Queue queue;
Consumer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
while(true){
queue.get();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.imooc.queue;
public class Producer implements Runnable{
Queue queue;
Producer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
int i=0;
while(true){
queue.set(i++);
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.imooc.queue;
public class Test {
public static void main(String[] args) {
Queue queue=new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
package com.imooc.queue;
public class Queue {
private int n;
public synchronized int get() {
System.out.println("消费:"+n);
return n;
}
public synchronized void set(int n) {
System.out.println("生产:"+n);
this.n = n;
}
}
Ps:会发生 生产了2次,消费了1次 或 生产了1次,消费了2次 等不想看到的状况。
package com.imooc.queue;
public class Queue {
private int n;
boolean flag=false;
public synchronized int get() {
if(!flag){
try {
wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费:"+n);
flag=false; // 消费完毕,容器中没有数据
notifyAll(); // 干脆全部唤醒,反正有flag作控制;如果不唤醒,可能会发生死锁
return n;
}
public synchronized void set(int n) {
if(flag){
try {
wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
flag=true; // 生产完毕,容器中已经有数据
notifyAll();
}
}
Ps:如果不加 notifyAll() 发生死锁,虽然加了同步锁,但是同步锁只是这里的 set 和 get 方法不会中途其他线程入侵,问题是在调用 set 的 wait 后,flag 还没来得及置换回来,get 的 wait 又执行了,这个概率还是很有可能发生的,所以导致死锁。
Ps:成功!!!