一.程序、进程、线程的概念
程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码, 静态对象。
进程(process) 是程序的一次执行过程, 或是正在运行的一个程序。
动态过程: 有它自身的产生、存在和消亡的过程。
- 如: 运行中的QQ
- 程序是静态的, 进程是动态的
线程(thread) 进程可进一步细化为线程, 是一个程序内部的一条执行路径
- 若一个程序可同一时间执行多个线程, 就是支持多线程的
二.使用多线程的优点
- 提高应用程序的响应。对图形化界面更有意义, 增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程, 独立运行, 利于理解和修改。
多线程应用场景:
- 程序需要同时执行两个或多个任务。
- 程序想要实现一些需要等待的任务时, 如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
三.Java中多线程的创建和使用
Java语言的JVM允许程序运行多个线程, 它通过java.lang.Thread类来实现
1.Thread类
Thread类的特性:
- 每个线程都是通过某个特定的Thread对象的 run() 方法来完成操作的, 经常把 run() 方法的主体称为线程体
- 通过该Thread对象的 start() 方法来调用这个线程
Thread类构造方法:
- Thread(): 创建新的Thread对象
- Thread(String threadname): 创建线程并制定线程实例名
- Thread(Runnable target): 指定创建线程的目标对象, 它实现了Runnable接口中的run方法
- Thread(Runnable target, String name): 同上, 创建新的Thread对象
2.创建线程的两种方式:
1.继承Thread类
- 定义子类继承Thread类
- 子类中重写Thread类中的 run() 方法
- 创建Thread子类对象, 即创建了线程对象。
- 调用线程对象 start() 方法: 启动线程, 调用 run() 方法
多线程: 继承Thread类的子类, 重写 run() 方法
/**
* 继承Thread类的方式进行多线程
* @author chenyao
*/
public class TestThread extends Thread {
String name = "默认";
public TestThread() {//构造方法
System.out.println("默认多线程");
}
public TestThread(String name) {
this.name = name;
System.out.println("这是多线程"+name);
}
@Override
public void run() {
System.out.println("线程运行的主逻辑");
for(int i = 0; i < 10; i++) {
System.out.println("这是"+name+"多线程的逻辑代码"+i);
}
super.run();
}
}
主线程: 创建多线程对象, 调用start()方法启动多线程
public class Test {
public static void main(String[] args) {
Thread t0 = new TestThread();
t0.start();//启动线程
Thread t1 = new TestThread("线程2");
t1.start();//启动多个线程
try {
t0.join();//多线程插入运行,只有多线程运行结束才会执行t0.join();后面的代码
} catch (InterruptedException e) {
e.printStackTrace();
}
//主线程在t0.start();之后的代码与t0的run()方法代码并行运行
//主线程与多线程运行按各自顺序,没有先后关系,这就是多线程的异步性
System.out.println("-------");
System.out.println("-------");
System.out.println("-------");
System.out.println("-------");
}
}
2.实现Runnable 接口
- 定义子类, 实现Runnable接口
- 子类中实现Runnable接口中的 run() 方法
- 通过Thread类含参构造器创建线程对象
- 将Runnable接口的子类对象作为实际参数传递类Thread类的构造方法中。(Thread类的构造方法接收Runnable接口的实现类对象)
- 调用Thread类的 start() 方法: 开启线程, 调用Runnable子类接口的 run() 方法
多线程: 创建子类实现Runnable接口, 实现 run() 方法
/**
* 通过实现Runnable接口进行多线程
* @author chenyao
*/
public class TestRunnable implements Runnable {
public TestRunnable() {
System.out.println("创建多线程"+Thread.currentThread().getName());
// //打印的线程为main,调用构造器的过程仍属于主线程
}
@Override
public void run() {
//run()方法里面为线程体,运行的代码属于多线程
System.out.println(Thread.currentThread().getName());//打印的线程名称为线程3
for(int i = 0; i < 10; i++) {
System.out.println("这是Runnable多线程的逻辑代码"+i);
}
}
}
主线程: 创建多线程对象, 调用 srart() 方法, 启动多线程
public class Test {
public static void main(String[] args) {
Test.testRunnable();
}
public static void testRunnable() {
// Thread t2 = new Thread(new TestRunnable());
// t2.start();
Thread t3 = new Thread(new TestRunnable(),"线程3");
// System.out.println(t3.getName());//
// System.out.println(Thread.currentThread().getName());//当先运行的线程名称,main
//Thread类的构造方法Thread(String threadname)
t3.start();
}
}
3.继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
区别:
- 继承Thread : 线程代码存放在Thread子类的run方法中。
- 实现Runnable : 线程袋面存放在Runnable接口子类的run方法中。
一般使用实现接口的方式来实现多线程
实现接口方法的好处:
- 避免了单继承的局限性(只能继承Thread一个父类, 实现方法不影响其继承其他父类)
- 多个线程可以共享同一个接口实现类的对象, 非常适合多个相同线程来处理同一份资源(子类中的同一个属性可以被多个线程调用)
(一个实现类可以创建多个实现类对象, 每个对象可以创建多个多线程)
/**
* 通过实现Runnable接口进行多线程
* @author chenyao
*/
public class TestRunnable implements Runnable {
int count = 0;//该属性可被同个TestRunnable对象下的多个线程共享
@Override
public void run() {
//run()方法里面为线程体,运行的代码属于多线程
System.out.println(Thread.currentThread().getName());//打印的线程名称为线程3
for(int i = 0; i < 10; i++) {
count++;
System.out.println("这是Runnable多线程的逻辑代码"+i+",count-"+count);
}
}
}
public class Test {
public static void main(String[] args) {
Test.testRunnable();
}
public static void testRunnable() {
TestRunnable trun = new TestRunnable();
//基于同一个实现类对象trun创建多个多线程
Thread t3 = new Thread(trun,"线程3");
Thread t4 = new Thread(trun,"线程4");
t3.start();
t4.start();
}
}
4.Thread类的有关方法
- void start() : 启动线程, 并执行对象的 run() 方法
- run() : 线程被调用时执行的操作
- String getName() : 返回线程的名称
- void setName() : 设置该线程的名称
- static currentThread() : 返回当前线程
- 线程优先级
- getPriority() : 返回线程优先级
- setPriority(int newPriority) : 设置线程的优先级
- 线程的调度
- static void yield() : 线程让步
- join() : 线程优先执行
- static void sleep(long mills) : 线程休眠 (指定时间: 毫秒)
- stop(): 强制线程生命期结束
- boolean isAlive() : 返回boolean值, 判断线程是否活动
5.线程的优先级、线程调度
1.线程的优先级:
Java中线程的优先级用数字1-10表示, 数字越大优先级越高,默认为5
优先级高则表示该线程有较大的概率优先被执行, 但不一定先执行
- MAX_PRIORITY(10)
- MIN_PRIORITY(1);
- NORM_PRIORITY(5);
涉及的方法:
- getPriority() : 返回线程优先级
- setPriority(int newPriority) : 设置线程的优先级
- 线程创建时继承父线程的优先级
public class TestThreadMethod {
public static void main(String[] args) {
TestRun run0 = new TestRun();
TestRun run1 = new TestRun();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
System.out.println("t0默认线程名称: "+t0.getName());//如果创建线程时没有指定名称,默认名称Thread-0
System.out.println("t1默认线程名称: "+t1.getName());//通过getName()方法获取线程名称,线程默认名称Thread-1
// t0.setName("线程t0");//设置线程名称
/**
* 线程的优先级, 就是哪个线程有[较大的概率]被执行
* 优先级用数字1-10表示,数字越大,优先级越高
* 默认优先级是5,线程创建时会继承父线程的优先级
*/
System.out.println("t0的默认优先级是:"+t0.getPriority());//获取线程的优先级
System.out.println("t1的默认优先级是:"+t1.getPriority());
t0.setPriority(10);//设置线程的优先级
t1.setPriority(1);
//优先级高,表示有较大的概率优先被执行,但不一定会先执行
t0.start();
t1.start();
System.out.println("====================");
System.out.println("====================");
System.out.println("====================");
}
}
class TestRun implements Runnable{
int count = 0;
public TestRun() {
System.out.println("创建多线程"+Thread.currentThread().getName());
}
@Override
public void run() {
for(int i = 0; i < 3; i++) {
count++;
System.out.println("这是多线程"+Thread.currentThread().getName()+"的逻辑代码:"+i+",count-"+count);
}
}
}
2.线程调度
涉及的方法:
static void yield() : 线程让步
- 暂停当前正在执行的线程, 把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程, 忽略此方法
join() : 线程插队(必须先启动线程)
- 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞(如main线程的后续的代码被阻塞), 直到 join() 方法加入的join线程执行完为止
- 低优先级的线程也可以获得优先执行
static void sleep(long mills) : 线程休眠 (指定时间: 毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制, 使其他线程有机会被执行, 时间到之后重新排队
- 抛出InterruptedException (线程中断) 异常
stop(): 强制线程生命期结束(不安全, 该方法已过时)
boolean isAlive() : 返回boolean值, 判断线程是否活着
interrupt(): 线程中断
boolean isInterrupt() : 返回boolean值, 判断线程是否被中断
public class TestThreadMethod {
public static void main(String[] args) {
TestRun run0 = new TestRun();
TestRun run1 = new TestRun();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
System.out.println("t0默认线程名称: "+t0.getName());//如果创建线程时没有指定名称,默认名称Thread-0
System.out.println("t1默认线程名称: "+t1.getName());//通过getName()方法获取线程名称,线程默认名称Thread-1
// t0.setName("线程t0");//设置线程名称
/**
* 线程的优先级, 就是哪个线程有[较大的概率]被执行
* 优先级用数字1-10表示,数字越大,优先级越高
* 默认优先级是5,线程创建时会继承父线程的优先级
*/
// System.out.println("t0的默认优先级是:"+t0.getPriority());//获取线程的优先级
// System.out.println("t1的默认优先级是:"+t1.getPriority());
// t0.setPriority(10);//设置线程的优先级
// t1.setPriority(1);
// //优先级高,表示有较大的概率优先被执行,但不一定会先执行
t0.start();
t1.start();
System.out.println("====================");
System.out.println("t1线程是否活着"+t1.isAlive());
t1.stop();//强制终止线程
// t1.interrupt();//线程中断
// System.out.println("t1线程是否被中断"+t1.isInterrupted());
try {
t0.join();//t0线程插队
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程插队后,调用线程(main线程)后续代码被阻塞,直至join线程执行完成
System.out.println("====================");
try {
System.out.println("====================1");
Thread.sleep(1000);//当前线程休眠1000ms,即设置时间间隔
System.out.println("====================2");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1线程是否活着"+t1.isAlive());//为什么还是存活??
System.out.println("t0线程是否活着"+t0.isAlive());//t0线程运行结束,所以返回值为false
Thread main = Thread.currentThread();//获取主线程对象
// main.stop();//可以对主线程进行操作
System.out.println("主线程是否活着"+main.isAlive());
}
}
class TestRun implements Runnable{
int count = 0;
public TestRun() {
System.out.println("创建多线程"+Thread.currentThread().getName());
}
@Override
public void run() {
for(int i = 0; i < 100; i++) {
count++;
if(Thread.currentThread().getName() == "Thread-0") {
Thread.yield();//线程让步
}
// if(i == 2) {
// Thread.currentThread().stop();
// }
System.out.println("这是多线程"+Thread.currentThread().getName()+"的逻辑代码:"+i+",count-"+count);
}
}
}
四.线程的生命周期
JDK用Thread.State枚举表示了线程的几种状态
要想实现多线程, 必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程, 在它的一个完整生命周期中通常要经历如下的五种状态:
- 新建(new Thread) : 当一个Thread类或其子类的对象被声明并创建时, 新生的线程对象处于新建状态
- 就绪(runnable) : 处于新建状态的线程被 start() 后, 将进入线程队列等待CPU时间片, 此时它已具备了运行的条件
- 运行(running) : 当就绪的线程被调度并获得处理器资源时, 便进入运行状态, run() 方法定义了线程的操作和功能
- 阻塞(blocked) : 在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态
- 死亡(dead) : 线程完成了它的全部工作或线程被提前强制性的终止
分为自然终止、异常终止
五.线程的同步
1.线程同步解决的问题
问题的提出:
- 多个线程执行的不确定性引起执行结果的不稳定。
- 多个线程对账本的共享, 会造成操作的不完整性, 会破坏数据。
解决方法: - 让一个线程都执行完毕, 在执行过程中, 其他线程不可以参与执行。
public class Test {
public static void main(String[] args) {
//定义银行账户对象
Account zhangsan = new Account(3000);
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User u_weixin = new User(zhangsan,2000);
User u_zhifubao = new User(zhangsan,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account{
public int money;
public Account(int money) {
this.money = money;
}
/**
* 多线程调用这个方法,存在问题,线程共享资源时,一个线程在执行方法(调用资源)没有完毕时,另一个线程又开始执行这个方法
* 由于多线程的异步性,对共享资源的操作就会存在问题
*
* 解决方法:让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
* @param drawMoney
*/
public void drawing(int drawMoney) {
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+money+"元");
if(drawMoney>0) {
if(drawMoney<=money) {
System.out.println(way+"操作,取款"+drawMoney+"元");
money -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+money+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+money+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
}
class User implements Runnable{//定义多线程实现类
Account account;
int money;
public User(Account account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
account.drawing(money);
}
}
2.同步机制
Java对于多线程的安全问题提供了专业的解决方式: 同步机制
Synchronized的使用方法:
- 给方法加上同步锁。
synchronized可以放在方法声明中, 表示整个方法为同步方法。
如:
public synchronized void show(String name){
…
} - 给代码块加上同步锁。
synchronized(对象){
//需要被同步的代码
}
注意:
* 在普通方法上加同步锁synchronized,锁的是调用方法的实例对象this。
* this对象所有加了synchronize的一般方法共用一把锁,所以一次只能访问一个方法
* 对象锁不住不同的对象
*
* 在静态方法上加同步锁synchronized, 锁的是整个类对象, 类似于(X.class)。
* 该类中所有加了synchronized的静态方法共用一把锁,一次只能调用一个
* 对该类所有对象有效, 因为调用的都是类方法, 操作全局变量。
*
特殊情况: 类锁和对象锁是两个不一样的锁,控制着不同的区域,两者互不干扰。
在线程获得对象锁的同时,也可以获得该实例的类锁,即同时获得两个锁,这是允许的。
1.普通方法加同步锁:
public class Test {
public static void main(String[] args) {
//定义银行账户对象
Account zhangsan = new Account(3000);
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User u_weixin = new User(zhangsan,2000);
User u_zhifubao = new User(zhangsan,2000);
//public drawing()方法前加synchronized同步锁锁的是Account对象zhangsan
//同一个Account对象,调用这个对象中所有加了synchronize的方法,只能一次调用一个
// Account lisi = new Account(3000);//public drawing()方法前加synchronized,对不同的Account对象无效
// User u_zhifubao = new User(lisi,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account{
public int money = 3000;
public Account(int money) {
this.money = money;
}
/**
* 多线程调用这个方法,存在问题,线程共享资源时,一个线程在执行方法(调用资源)没有完毕时,另一个线程又开始执行这个方法
* 由于多线程的异步性,对共享资源的操作就会存在问题
*
* 解决方法:让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
* 通过synchronized同步锁来完成
* 1.可以直接在方法前加上synchronized关键字
* 在普通方法上加同步锁synchronized,锁的是调用方法的实例对象this。锁不住不同的对象
* 同时,this对象中所有加了synchronize的方法只能一次调用一个
*
* 在静态方法上加同步锁synchronized,锁的是整个类对象,类似于(X.class)。对所有对象有效
* 该类中所有加了synchronized的静态方法,一次只能调用一个
* @param drawMoney
*/
public synchronized void drawing(int drawMoney) {
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+money+"元");
if(drawMoney>0) {
if(drawMoney<=money) {
System.out.println(way+"操作,取款"+drawMoney+"元");
money -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+money+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+money+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
}
class User implements Runnable{//定义多线程实现类
Account account;
int money;
public User(Account account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
account.drawing(money);
}
}
2.静态方法同步锁:
public class Test1 {
public static void main(String[] args) {
//定义不同的Account类对象
Account1 zhangsan = new Account1();
Account1 lisi = new Account1();
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User1 u_weixin = new User1(zhangsan,2000);
User1 u_zhifubao = new User1(lisi,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account1{
public static int stcmoney = 3000;//全局变量
/**
* 在静态方法上加同步锁synchronized,锁的是整个类对象,类似于(X.class)。对所有对象有效
* 该类中所有加了synchronized的静态方法,一次只能调用一个
* @param drawMoney
*/
public static synchronized void drawing(int drawMoney) {
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
if(drawMoney>0) {
if(drawMoney<=stcmoney) {
System.out.println(way+"操作,取款"+drawMoney+"元");
stcmoney -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
//静态方法加同步锁synchronized
public static synchronized void drawing1(int drawMoney) {
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
if(drawMoney>0) {
if(drawMoney<=stcmoney) {
System.out.println(way+"操作,取款"+drawMoney+"元");
stcmoney -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
}
class User1 implements Runnable{//定义多线程实现类
Account1 account;
int money;
public User1(Account1 account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
if(Thread.currentThread().getName()=="微信") {
Account1.drawing(money);//调用不同的静态方法,同步锁仍然生效
}else {
Account1.drawing1(money);//调用不同的静态方法,同步锁仍然生效
}
}
}
3.代码块加同步锁(根据当前对象)
使用synchronized (this) {} 代码块, 表示当前对象的代码块被加了同步锁
- 用this代码块是表示当前的对象, 有一个调用则当前对象即被锁定, 传入不同的对象共用一把锁
- 其他的方法中的synchronized (this){}代码块,共用一个同步锁
- 类似于类锁, 因为只针对代码块之内的代码, 所以比类锁效率更高
public class Test2 {
public static void main(String[] args) {
Account2 zhangsan = new Account2();
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User2 u_weixin = new User2(zhangsan,2000);
User2 u_zhifubao = new User2(zhangsan,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account2{
public static int stcmoney = 3000;//全局变量
/**
* 代码块上加同步锁synchronized
* @param drawMoney
*/
public void drawing(int drawMoney) {
synchronized (this) {
// 用this代码块是表示当前的对象, 有一个调用则当前对象即被锁定, 传入不同的对象共用一把锁
// 其他的方法中的synchronized (this){}代码块,共用一个同步锁
// 类似于类锁, 因为只针对代码块之内的代码, 所以比类锁效率更高
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
if(drawMoney>0) {
if(drawMoney<=stcmoney) {
System.out.println(way+"操作,取款"+drawMoney+"元");
stcmoney -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
}
}
class User2 implements Runnable{//定义多线程实现类
Account2 account;
int money;
public User2(Account2 account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
account.drawing(money);
}
}
4.代码块加同步锁(根据传入的不同对象)
synchronized修饰代码块, 想要根据不同对象有不同的锁, 则需要创建 代码块所在的类 的不同的对象, 并且使用 synchronized (obj) {} 以传入不同的对象
- 用obj代码块是表示传入的对象, 每个对象分配一把锁, 不同的对象间互不干扰
- 锁定的对象中, 其他的方法中的synchronized (this){}代码块,共用一个同步锁
- 类似于对象锁, 因为只针对代码块之内的代码, 所以比对象锁效率更高
public class Test3 {
public static void main(String[] args) {
//创建不同的Account对象
Account3 zhangsan = new Account3();
Account3 lisi = new Account3();
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User3 u_weixin = new User3(zhangsan,2000);
User3 u_zhifubao = new User3(lisi,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account3{
public static int stcmoney = 3000;//全局变量
/**
* synchronized修饰代码块,想要根据不同对象有不同的锁
* @param drawMoney
*/
public void drawing1(int drawMoney,Account3 a) {
synchronized (a) {
// 用obj代码块是表示传入的对象, 每个对象分配一把锁, 不同的对象间互不干扰
// 锁定的对象中, 其他的方法中的synchronized (this){}代码块,共用一个同步锁
// 类似于对象锁, 因为只针对代码块之内的代码, 所以比对象锁效率更高
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
if(drawMoney>0) {
if(drawMoney<=stcmoney) {
System.out.println(way+"操作,取款"+drawMoney+"元");
stcmoney -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
}
}
}
class User3 implements Runnable{//定义多线程实现类
Account3 account;
int money;
public User3(Account3 account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
account.drawing1(money,account);
}
}
5.小结
普通方法加同步锁:
锁的是当前对象, 当前对象的所有加了同步锁的方法共用一个同步锁
静态方法加同步锁:
该类所有对象共用一个同步锁
代码块加同步锁——synchronized (this) {} 形式:
所有执行代码块的当前对象, 执行的所有synchronized (this) {} 代码块共用一个同步锁
代码块加同步锁——synchronized (obj) {} 形式:
根据不同的对象有不同的锁
6.线程的死锁问题
死锁的概念:
- 不同的线程分别占用对方需要的同步资源不放弃, 都在等待对方放弃自己需要的同步资源, 就形成了线程的死锁
解决方法:
- 专门的算法、原则, 比如加锁顺序一致
- 尽量减少同步资源的定义, 尽量避免锁未释放的场景
六.线程的通信
wait() 与 notify() 和 notifyAll()
使线程从运行进入阻塞——以及从阻塞进入就绪状态
- wait() : 令当前线程挂起并放弃CPU、同步资源, 使别的线程可访问并修改共享资源, 而当前线程排队等候 再次对资源的访问
- notify() : 唤醒正在排队等待同步资源的线程中 优先级最高者, 结束等待
- notifyAll() : 唤醒正在排队等待资源的所有线程结束等待
java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用, 否则会报java.lang.IllegalMonitorStateException(监控器非法)异常
1.wait()方法
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态, 直到另一线程对该对象发出 notify (或者notifyAll) 为止。
调用方法的必要条件: 当前线程必须具有对该对象的监控权 (加锁)
调用此方法后, 当前线程将释放对象监控权, 然后进入等待
在当前线程被notify后, 要重新获得监控权, 然后从断点处继续代码的执行
2.notify()/notiifyAll()方法
在当前线程中调用方法: 对象名.notify()
功能: 唤醒等待该对象监控权的一个线程
调用方法的必要条件: 当前线程必须具有对该对象的监控权 (加锁)
public class Test2 {
public static void main(String[] args) {
Account2 zhangsan = new Account2();
//多线程对象,
//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
//如果需要传入的参数不同,则需要两个对象
User2 u_weixin = new User2(zhangsan,2000);
User2 u_zhifubao = new User2(zhangsan,2000);
Thread weixin = new Thread(u_weixin,"微信");
Thread zhifubao = new Thread(u_zhifubao,"支付宝");
weixin.start();
zhifubao.start();
}
}
class Account2{
public static int stcmoney = 3000;//全局变量
boolean flag = true;
/**
* 代码块上加同步锁synchronized
* @param drawMoney
*/
public void drawing(int drawMoney) {
synchronized (this) {//表示当前对象的代码块被加了同步锁
//用this代码块是表示当前的对象, 不同的对象调用的是同一个同步锁
//如果在其他的方法中也有synchronized (this){}代码块,使用的也都是同一个同步锁
/**
* 线程的通信,wait() 与 notify() 和 notifyAll()
* 需求: 如果是微信操作,先不执行,等支付宝操作,支付宝操作完微信再继续
* 实现: 首先判断,如果当前线程名称是"微信",则wait(),并放弃当前占有的同步锁,进入阻塞状态。
* "支付宝"线程会抢占同步锁,并重新执行同步锁的代码,当前线程名称为"支付宝"
* 再次判断,当前线程名称为"支付宝",notify()唤醒优先级最高的进程,此时"微信"被唤醒执行同步锁中的后续代码
*/
String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
if(flag && way.equals("微信")) {//flag确保判断只进行一次,避免支付宝线程完毕后微信线程运行受阻
try {
System.out.println(way+"进入等待");
this.wait();//当前的线程进入等待的阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = false;
System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
if(drawMoney>0) {
if(drawMoney<=stcmoney) {
System.out.println(way+"操作,取款"+drawMoney+"元");
stcmoney -= drawMoney;
System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
}else {
System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
}
}else {
System.out.println("取款金额必须为正数");
}
if(way.equals("支付宝")) {
System.out.println("唤醒线程"+way);
// this.notify();//唤醒当前优先级最高的线程,进入就绪状态
this.notifyAll();//唤醒当前所有线程,进入就绪状态
}
}
}
}
class User2 implements Runnable{//定义多线程实现类
Account2 account;
int money;
public User2(Account2 account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
account.drawing(money);
}
}
七.经典例题:生产者/消费者问题
生产者(Producer)将产品交给店员(Clerk), 而消费者(Customer)从电源处取走产品, 店员一次只能持有固定数量的产品(比如:20), 店员会叫生产者听一下, 如果店中有空位放产品了再通知生产者继续生产; 如果店中没有产品, 店员会告诉消费者等一下, 如果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
- 生产者比消费者快时, 消费者会漏掉一些数据没有取到
- 消费者比生产者快时,消费者会取到相同的数据
/**
* 生产者与消费者问题
* @author chenyao
*
*/
public class Test4 {
public static void main(String[] args) {
Clerk c = new Clerk();
//消费时不生产,生产时不消费
//生产者
new Thread(new Runnable() {
@Override
public void run() {
synchronized(c) {
while(true) {//无限循环,代表无限的生产次数
if(c.productNum == 0) {//产品数为0,开始生产
System.out.println("产品数为0,开始生产");
while(c.productNum < 4) {//产品数<4,一直生产
c.productNum ++;//生产
System.out.println("库存:"+c.productNum);
}
System.out.println("产品数为"+c.productNum+",结束生产");
c.notify();//唤醒消费者
}else {
try {
c.wait();//让生产者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
},"生产者").start();
//消费者
new Thread(new Runnable() {
@Override
public void run() {
synchronized(c) {
while(true) {//无限循环,代表无限的消费次数
if(c.productNum == 4) {//产品数为4,开始消费
System.out.println("产品数为4,开始消费");
while(c.productNum > 0) {//产品数>0,一直消费
c.productNum --;//消费
System.out.println("库存:"+c.productNum);
}
System.out.println("产品数为"+c.productNum+",结束消费");
c.notify();//唤醒生产者
}else {
try {
c.wait();//让消费者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
},"消费者").start();
}
}
class Clerk{
public int productNum = 0;
}