多线程-学习笔记
程序:程序是一段静态的代码,它是应用程序执行的蓝本
进程:进程是指一种正在运行的程序,有自己的地址空间;
进程特点:1、动态性
2、并发性
3、独立性
线程的定义:进程内部的一个执行单元,它是程序中一个单一的顺序控制流。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。
进程是系统资源分配的单位,可包括多个线程;
线程是独立调度和分派的基本单位,共享进程资源;
引入进程是为了多个程序并发执行,提高资源的利用率和系统吞吐量;
引入线程是为了减少程序在并发执行时付出的时空开销;
线程分类:
系统级线程(核心级线程):由操作系统内核进行管理,使用户程序可以创建、执行、撤销线程;
用户级线程:管理过程全部由用户程序完成,操作系统内核只对进程进行管理;
多线程的优势:
1、多线程使系统空转时间减少,提高CPU利用率;
2、进程间不能共享内存,但线程之间共享内存非常容易;
3、使用多线程实现多多任务并发比多进程的效率高;
4、JAVA语言内置多线程功能支持,简化了JAVA的多线程编程
创建线程的两种方法:
1、继承Java.lang.Thread类,并覆盖run()方法;
1 package test;
2
3 public class MyThreadTest1 extends Thread {
4
5 @Override
6 public void run() {
7 // 覆盖方法
8 }
9 }
2、实现Java.lang.Runnable接口,并实现run()方法;
package test;
public class MyThreadTest2 implements Runnable {
@Override
public void run() {
//实现该方法
}
}
启动线程:
新建的线程不会自动开始运行,必须通过start()方法启动(直接调用run()方法不是启动线程)
1、启动继承Thread的线程
MyThreadTest1 mtt1=new MyThreadTest1();
mtt1.start();
2、启动实现Runnable接口的线程
MyThreadTest2 mtt2=new MyThreadTest2();
Thread thread=new Thread(mtt2);//这里需要把实现Runnable接口的对象传入,再介由Threa对象启动
thread.start();
例子1:
1 package test;
2
3 public class ThreadDemo1 {
4
5 /**
6 * @param args
7 */
8 public static void main(String[] args) {
9 //main方法也是一个线程
10 MyThread1 mt1=new MyThread1();
11 mt1.start();
12 while(true){
13 System.out.println("兔子领先了,加油啊!");
14 }
15
16 }
17
18 }
19 class MyThread1 extends Thread{
20
21 @Override
22 public void run() {
23 while(true){
24 System.out.println("乌龟超过了,再接再厉!");
25 }
26 }
27
28 }
例子2:
1 package test;
2
3 public class ThreadDemo2 {
4
5 /**
6 * @param args
7 */
8 public static void main(String[] args) {
9 MyThread2 mt2=new MyThread2();
10 Thread thread=new Thread(mt2);
11 thread.start();
12 while(true){
13 System.out.println("兔子领先了,加油啊!");
14 }
15
16 }
17
18 }
19
20 class MyThread2 implements Runnable{
21
22 @Override
23 public void run() {
24 while(true){
25 System.out.println("乌龟超过了,再接再厉!");
26 }
27
28 }
29
30 }
两种线程创建方式比较:
继承Thread类方式的多线程:
优势:编写简单
劣势:无法继承其他父类
实现Runnble接口方式的多线程:
优势:可以继承其它类,多线程可共享同一个Thread对象
劣势:编程方式稍微复杂,如果要访问当前线程,需要调用Thread.cuurrentThread()方法
Thread类的常用方法
方法 功能
static Thread currentThread() 得到当前线程
final String getName() 返回线程的名称
final void setName(String name) 将线程的名称设置为由name指定的名称
void start() 调用run()方法启动线程,开始线程的执行
void run() 存放线程体代码
线程的状态
新生:使用new 关键字创建一个线程后,尚未调用其start方法之前
可运行:调用线程对象的start方法之后;
这个状态当中,线程对象可能正在运行,也可能等待运行
阻塞:一种“不可运行”的状态,在得到一个特定的事件之后会返回到可运行状态
死亡:线程的run方法运行完毕或者在运行中出现未捕获的异常时
线程优先级概述
每个线程执行时都具有一定的优先级。当调度线程时会优先考虑级别高的线程。
默认情况下,一个线程继承其父线程的优先级。
使用线程对象.setPriority(int p)来改变线程的优先级
优先级影响CPU在线程间切换,切换的原则是:
当一个线程通过显式放弃、睡眠、或者阻塞、自愿释放控制权时,所有线程均接受检查而优先级高线程将会优先执行
一个线程可以被一个高优先级的线程抢占资源
同级别的线程之间,则通过控制权释放,确保所有的线程均有机会运行。
线程调度:
thread.join()阻塞主线程,该线程执行完毕再执行主线程,在start()方法之后调用有效
Thread.sleep(int millisecond)阻塞当前线程一定时间,时间过后转为可运行状态。线程体内部调用
Thread.yield()停止当前线程,线程状态变为可运行状态。线程体内部调用
thread.setDaemon(true)可以将指定线程设置成后台线程,创建后台线程的线程结束时,后台线程也随之消亡。在start()方法之前调用有效
线程同步:
当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全
当两个或两个以上线程访问同一资源时,需要某种方法来确保资源在某一时刻只被一个线程使用
线程同步的实现方案
1、同步代码块
2、同步方法
例子:
创建一个账户类
1 package test;
2
3 /**
4 * 账户类
5 * @author Administrator
6 *
7 */
8 public class Account {
9
10 //余额
11 private int balance=1000;
12
13
14 public int getBalance() {
15 return balance;
16 }
17
18
19 /**
20 * 取款
21 * @param amount
22 */
23 public void withdraw(int amount){
24 balance = balance - amount;
25 }
26
27 }
创建一个取款的线程类
package test;
/**
* 取款线程
* @author Administrator
*
*/
public class WithdrawThread implements Runnable {
private Account acct=new Account();
public void run(){
for (int i = 0; i < 10; i++) {
withdraw(100);
if(acct.getBalance()<0){
System.out.println("账户透支了!!");
}
}
}
private void withdraw(int amount){
if(acct.getBalance() >= amount){
System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
acct.withdraw(amount);
System.out.println(Thread.currentThread().getName()+"完成取款");
}else{
System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
}
}
}
创建测试类,创建两个线程同时对同一个账号操作
package test;
public class TestWithdraw {
/**
* @param args
*/
public static void main(String[] args) {
WithdrawThread wt=new WithdrawThread();
Thread zhangsan=new Thread(wt);
zhangsan.setName("张三");
Thread qizi=new Thread(wt);
qizi.setName("张三的妻子");
zhangsan.start();
qizi.start();
}
}
测试结果有可能出现透支的情况,为了避免这种情况对取款线程类进行修改:
第一种解决方式:同步代码块
1 package test;
2
3 /**
4 * 取款线程
5 * @author Administrator
6 *
7 */
8 public class WithdrawThread implements Runnable {
9
10 private Account acct=new Account();
11
12 public void run(){
13 for (int i = 0; i < 10; i++) {
14 withdraw(100);
15 if(acct.getBalance()<0){
16 System.out.println("账户透支了!!");
17 }
18 }
19 }
20
21 private void withdraw(int amount){
22 synchronized (acct) {
23 if(acct.getBalance() >= amount){
24 System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
25 acct.withdraw(amount);
26 System.out.println(Thread.currentThread().getName()+"完成取款");
27 }else{
28 System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
29 }
30 }
31 }
32
33 }
第二种解决方式:同步方法
1 package test;
2
3 /**
4 * 取款线程
5 * @author Administrator
6 *
7 */
8 public class WithdrawThread implements Runnable {
9
10 private Account acct=new Account();
11
12 public void run(){
13 for (int i = 0; i < 10; i++) {
14 withdraw(100);
15 if(acct.getBalance()<0){
16 System.out.println("账户透支了!!");
17 }
18 }
19 }
20
21 private synchronized void withdraw(int amount){
22
23 if(acct.getBalance() >= amount){
24 System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
25 acct.withdraw(amount);
26 System.out.println(Thread.currentThread().getName()+"完成取款");
27 }else{
28 System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
29 }
30 }
31
32 }
这样就不会出现透支的情况了。
线程同步的好处:
解决了线程安全问题
线程同步的缺点:
性能下降
会带来死锁
死锁:
当两个线程相互等待对方释放“锁”时就会发生死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
多线程编程时应该注意避免死锁的发生
线程间通信的必要性
生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互信赖,互为条件
对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了3个方法解决线程之间的通信
方法名 | 作用 |
final void wait() | 表示线程一直等待,直到其他线程通知 |
void wait(long timeout) | 线程等待指定的毫秒数的时间 |
final void wait(long timeout,int nanos) | 线程等待指定毫秒、微秒的时间 |
final void notify() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 |
以上几个方法均是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常
示例:
1 package test;
2
3 /**
4 * 共享资源类
5 * @author Administrator
6 *
7 */
8 public class SharedDate {
9
10 private char c;
11
12 private boolean isProduced=false;
13
14 public synchronized void putSharChar(char c){
15 if(!isProduced){
16 this.c=c;
17 isProduced=true;
18 System.out.println("生产了产品"+c);
19 notify();//通知消费者,可以消费
20 }
21 try {
22 System.out.println("等待消费者消费。。。。。");
23 wait();
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27
28 }
29
30 public synchronized char getSharChar(){
31 if(isProduced){
32 isProduced=false;
33 System.out.println("消费者消费了产品"+c);
34 notify();//通知生产者需要生产
35 }
36 try {
37 System.out.println("等待生产者生产。。。。");
38 wait();
39 } catch (InterruptedException e) {
40 e.printStackTrace();
41 }
42 return this.c;
43 }
44
45 }
1 package test;
2
3 /**
4 * 生产者线程
5 * @author Administrator
6 *
7 */
8 public class ProducerThread extends Thread {
9
10 private SharedDate s;
11
12 public ProducerThread(SharedDate s){
13 this.s=s;
14 }
15
16 public void run(){
17 for(char ch='A';ch<='D';ch++){
18 s.putSharChar(ch);//生产产品将产品放入仓库
19 }
20 }
21 }
1 package test;
2
3 /**
4 * 消费者线程
5 * @author Administrator
6 *
7 */
8 public class ConsumerThread extends Thread {
9
10 private SharedDate s;
11
12 public ConsumerThread(SharedDate s){
13 this.s=s;
14 }
15
16 public void run(){
17 char ch;
18 do{
19 ch=s.getSharChar();//消费
20 }while(ch!='D');
21 }
22
23 }
1 package test;
2
3 /**
4 * 测试类
5 * @author Administrator
6 *
7 */
8 public class CommunicationDemo {
9
10 /**
11 * @param args
12 */
13 public static void main(String[] args) {
14 SharedDate s=new SharedDate();//共享同一个共享资源
15
16 new ProducerThread(s).start();//生产者线程
17 new ConsumerThread(s).start();//消费者线程
18
19
20 }
21
22 }
完