一,简介
1.1 线程和进程的区分
- 进程是一个“进行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
- 一个进程可以有多个线程,但必定有一个主线程
- 一个线程必定只属于一个进程
- 线程间可以使用进程数据段来进行通信,二进程需要使用IPC(Inter-Progress Communication)机制
1.2 线程的分类
线程=守护线程+用户线程
Java在start()前调用thread.setDarmon(true)可以把用户线程变为守护线程
1.3 使用多线程的优点
- 提高应用程序的响应,对图形化界面更有意义,增强用户体验
- 提高计算机CPU利用率
- 改善程序结构,将长且复杂的程序分为多个线程独立运行有利于理解和修改
二、java中多线程的创建和使用
- 一个线程对象只能执行一次start()
2.1 创建线程的两种方法(继承Thread和实现Runable)
/*
继承Thread的线程的使用方法
①.创建一个继承Thread的子类
②.重写run()方法
③.主线程中创建子类对象(对象作为参数传给Thread的构造器,从而new一个Thread)
④.调用对象的start()方法:启动此线程---->调用相应run()方法
*/
public class TestThread {
public static void main(String[] args) {
// 3.创建子类对象
SubThread st = new SubThread();
// 4.启动子线程
st.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "\t:" + i);
if (i % 10 == 0) {
Thread.yield();
}
}
}
}
// 使用Thread子类创建方法:
// 1.创建Thread子类
class SubThread extends Thread {
// 2.重写run()方法
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "\t:" + i);
}
}
}
/*
实现Runnable接口的创建方法:
①创建一个实现Runnable接口的子类
②重写run();
③主线程中创建子类对象,创建Thread对象并作为形参传给Thread的构造器
④通过Thread对象调用方法
*/
public class TestSubRunnabe {
public static void main(String[] args) {
SubRunnable s1=new SubRunnable();
//SubRunnable s2=new SubRunnable();
Thread t1=new Thread(s1);
Thread t2=new Thread(s1);
t1.start();//执行的是s的run()
t2.start();
}
}
//使用Runable创建方法:
class SubRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "\t:" + i);
if (i % 10 == 0) {
Thread.yield();
}
}
}
}
2.2 两个方法之间的关系:
- Thread 实现了 Runnable 接口
- 实现的方式优于继承,因为单继承的局限性
- 如果多个线程操作同一份资源(共享数据),更适合使用实现的方式(只创建了一个对象)
2.3 Thread类的主要方法
start():void
;启动线程并执行run()run():void
;子线程要执行的代码currentThread():Thread
;调取当前线程。返回当前ThreadgetName():String
;获取此线程名字setName(String str):void
;设置此线程名字yield()
;调用此方法的线程释放当前CPU的执行权join():void
;在A线程中调用B线程的join方法,A线程停止执行直至B执行完isAlive():boolean
;是否活着sleep(long ms):void
;显式让当前线程睡眠(ms)
2.4 线程调度与设置优先级
Ⅰ .时间片式
同优先级
Ⅱ.抢占式
高优先级
getPriority():int;获取优先级
setPriority(int newPriority):void;改变优先级
三 、线程生命周期
枚举类:Thread.state枚举类:
NEW:新建
TIMED_WAITING:
WAITING:就绪
RUNNABLE:运行
BLOCKED:阻塞
TERMINATED:终止
四、线程的同步
4.1 为什么要使用线程同步
- 由于一个线程在操作共享数据中,未操作完的情况下,另一个进程参与进来,导致共享数据存在安全问题
4.2 解决方式一:同步代码块
/*
*synchronized(同步监视器){
* //需要被同步的代码块(操作共享数据的代码)
*}
*
* 银行有一个账户:
* 有两个储户分别向同一个账户存3000,每次1000,每次存完打印账户余额
*
* 有无多线程:两个储户,有
* 有无共享数据:一个账户,有---->考虑线程同步
*/
public class TestAccount {
public static void main(String[] args) {
//这里只创建了一个Customer对象,所以其内部可以使用this当锁
Customer c=new Customer(new Account());
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.setName("customer1");
t2.setName("customer2");
t1.start();
t2.start();
}
}
class Account {
double balance = 0;
public Account() {
}
public void deposit(double amt) {
balance += amt;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",current balance is: " + balance);
}
}
class Customer implements Runnable {
Account account;
public Customer(Account acc){
this.account=acc;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
synchronized (this) {
account.deposit(1000);
}
}
}
}
说明:
- 共享数据:多个线程共同操作的同一个数据(变量)。
- 同步监视器:任何一个类的对象。哪个线程获取此监视器,就执行被同步的代码。俗称:锁。
- 在实现的方式中,可以考虑用this来充当锁,继承方式实现多线程,因为多个线程中有多个对象,所以不能用this当锁,可以使用一个static 对象。
4.3 解决方式二:同步方法(只能用在实现方式)
/*
*public synchronized void method(){
* //需要被同步的代码块
*}
*1. 此同步方法能保证当其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法
*2. 此方法的锁为this,所以只能用在实现方式
*
* 银行有一个账户:
* 有两个储户分别向同一个账户存3000,每次1000,每次存完打印账户余额
*
* 有无多线程:两个储户,有
* 有无共享数据:一个账户,有---->考虑线程同步
*/
public class TestAccount1 {
public static void main(String[] args) {
//创建一个账户
Account1 account = new Account1();
//创建两个客户
Customer1 cus1 = new Customer1(account, "cus1");
Customer1 cus2 = new Customer1(account, "cus2");
cus1.start();
cus2.start();
}
}
class Account1 {
double balance = 0;
public Account1() {
}
//此方法执行时默认使用this当锁,在同一时刻,只能有一个线程执行该方法
public synchronized void deposit(double amt) {
balance += amt;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",current balance is: " + balance);
}
}
class Customer1 extends Thread {
Account1 account;
public Customer1(Account1 account, String name) {
super(name);
this.account = account;
}
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
说明:
- 对于一般方法,可以使用同步代码块,默认为this
- 对于静态方法,可以使用同步代码块,默认当前类本身当锁: ClassName.class
- 线程同步的弊端:由于同一个时间只有一个线程访问共享数据,效率变低
- 释放锁的时机:代码块执行完毕或者执行wait()
4.4 死锁(DeadLock):
不同线程分别占用对方需要的资源不放弃,都在等待对方放弃,形成死锁
解决办法:专门的算法,原则,尽量减少同步资源的定义
死锁的一个例子:
public class TestDeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized (sb1) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("a");
//代码执行到这里时,握着sb1锁,等待sb2锁
synchronized (sb2) {
sb2.append("b");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (sb2) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("c");
//代码执行到这里时,握着sb2锁,等待sb1锁
synchronized (sb1) {
sb2.append("d");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
五、线程的通信
- wait():void
:当前线程挂起并放弃CPU,同步资源,排队等候,被唤醒后继续执行wait()后的代码
- notify():void
:唤醒正在排队的等待同步资源的最高优先级线程结束等待
- notifyAll():void
:唤醒正在排队等待资源的全部线程结束等待
java.lang.Object下的此三个方法只有在synchronized方法或代码块中才能使用,否则报异常:java.lang.IllegalMonitorStateException
多线程例题:
1. 两个线程交替打印数据
2. 生产者与消费者的问题