多线程基本概念
程序
对应于操作系统中的一个可执行文件,启动后将程序加载到内存,开始执行该程序,于是产生了“进程”。
进程
就是执行中的程序,是一个动态的概念。本质是一个在内存中独立运行的程序空间。
进程是程序的一次动态执行过程,占用特定的地址空间。
有三部分组成:cpu,data,code。每个进程是独立的,保有自己的cpu时间,代码和数据,缺点是:浪费内存,cpu的负担较重。
多任务操作系统将CPU时间动态划分给每个进程,同时执行多个进程,每个进程独立运行。
线程
一个进程产生多个线程。同一进程的多个线程也可以共享此进程的某些资源(如:代码、数据),所以线程又被称为轻量级进程。
一个进程内部的一个执行单元,是程序中的一个单一的顺序控制流程。
一个进程可拥有多个并行的线程。
一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且他们从同一堆中分配对象并进行通信、数据交换和同步操作。
线程间通信是在同一地址空间上进行的,不需要额外的通信机制,所以线程间传递速度更快。
线程和进程的区别
线程在进程中运行
一个进程包含多个线程
不同进程间数据很难共享,而同一进程下的不同线程间数据很易共享。
进程要比线程消耗更多的计算机资源。
进程间不会相互影响,因为空间完全隔离,而进程中的一个线程挂掉会导致整个进程挂掉。
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束才能使用这块内存。
一个进程如果只有一个线程则可以看作单线程,有多个线程,进程的执行过程不是一条线的,而是多条线共同完成。
什么是主线程已经子线程
主线程
当java程序启动时,一个线程会立刻运行,该线程叫做程序的主线程,即main方法对应的线程,是程序开始时执行的。
main方法是一个应用的入口,代表了应用的主线程。JVM执行main方法时,main方法会进入到栈内存,JVM会通过操作系统开辟一条main方法通向cpu执行路径,cpu就会通过这路径执行main方法,这个路径叫main(主)线程。
主线程特点:
是产生其他子线程的线程。
不一定是最后完成执行的线程,子线程可能在他接受后还在运行。
子线程
在主线程中创建并启动的线程一般称为子线程。
线程创建
1、通过继承Thread类实现多线程
java.lang.Thread类是负责实现多线程的类。
继承Thread类实现多线程的步骤:
继承Thread类定义线程类。
重写Thread类中的run()方法。run()方法也称为线程体。
实例化线程类并通过start()方法启动线程
public class TestThread extends Thread {
public TestThread(){
System.out.println(this.getName());
}
/**
* 线程的线程体
*/
@Override
public void run() {
System.out.println(this.getName()+"线程开始");
for(int i=0;i<20;i++){
System.out.println(this.getName()+" "+i);
}
System.out.println(this.getName()+"线程结束");
}
public static void main(String[] args) {
System.out.println("主线程开始");
TestThread t1 = new TestThread();
//启动线程
t1.start();
TestThread t2 = new TestThread();
//启动线程
t2.start();
System.out.println("主线程结束");
}
}
2、通过实现Runnable接口实现多线程
public class TestThread2 implements Runnable {
public TestThread2(){
System.out.println(Thread.currentThread().getName());
}
/**
*当前线程的线程体方法
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始");
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public static void main(String[] args) {
System.out.println("主线程开始");
TestThread2 testThread2 = new TestThread2();
Thread t1 = new Thread(testThread2);
t1.start();
Thread t2 = new Thread(new TestThread2());
t2.start();
System.out.println("主线程结束");
}
}
线程执行流程
线程生命周期
一个线程对象在它的生命周期内,需要经历5个状态。
新生状态(New)
用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。,当系统选定一个等待执行
的Thread 对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4中原因会导致线程进入就绪状态:
新建线程:调用start()方法,进入就绪状态;
阻塞线程:阻塞解除,进入就绪状态;
运行线程:调用yield()方法,直接进入就绪状态;
运行线程:JVM将CPU资源从本线程切换到其他线程。
运行状态(Running)
执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而堵塞或完成任务而死亡。如果在时间片内没有执行结束,会被系统换下来到就绪状态。
阻塞状态(Blocked)
指的是暂停一个线程的执行以等待某个条件发生。4种原因导致阻塞:
执行sleep方法,使当前线程休眠,进入阻塞状态。指定时间到了后进入就绪状态。
执行wait()方法,进入阻塞状态。当使用nofity()方法唤醒线程后进入就绪状态。
线程运行时某个操作进入阻塞状态,比如执行IO流操作(read()/write()本身就是阻塞的方法)。只有引起阻塞的原因消失后线程才进入就绪状态。
join()线程联合:某个线程等待另一个线程执行结束后才能继续执行时,使用join()方法。
死亡状态(Terminated)
线程生命周期的最后一个阶段。死亡原因有两个:
1、正常运行的线程完成了run()方法内的全部工作;
2、线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(这两个方法已被JDK废弃,不推荐使用)
当一个线程进入死亡状态后就不能回到其他状态了。
线程使用
终止线程
由于stop()或destroy()方法已被JDK废弃,通常终止线程的方法是提供一个boolean型的终止变量,当这个变量为false时,终止线程的执行。
public class StopThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程开始");
int i= 0;
while(flag){
System.out.println(Thread.currentThread().getName()+" "+i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public void stop(){
this.flag = false;
}
public static void main(String[] args)throws Exception {
System.out.println("主线程开始");
StopThread st = new StopThread();
Thread t1 = new Thread(st);
t1.start();
System.in.read();
st.stop();
System.out.println("主线程结束");
}
}
暂停当前线程执行sleep/yield
两个方法区别:
sleep()方法:让正在运行的线程进入阻塞状态,知道休眠时间满,进入就绪状态。
yield()方法:让正在运行的线程进入就绪状态,让出CPU使用权。
sleep方法使用
public class SleepThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程开始");
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public static void main(String[] args) {
System.out.println("主线程开始");
Thread t = new Thread(new SleepThread());
t.start();
System.out.println("主线程结束");
}
}
yield方法使用
作用:暂停当前执行线程,并执行其他线程。
目的是让具有相同优先级的线程之间能够适当的轮换执行。实际中无法保证 yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
注意:
yield 是一个静态的方法。
调用 yield 后,yield 告诉当前线程把运行机会交给具有相同优先级的线程。
yield 不能保证,当前线程迅速从运行状态切换到就绪状态。
yield 只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。
public class YieldThread implements Runnable {
@Override
public void run() {
for(int i=0;i<30;i++){
if("Thread-0".equals(Thread.currentThread().getName())){
if(i == 0){
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName()+""+i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new YieldThread());
Thread t2 = new Thread(new YieldThread());
t1.start();
t2.start();
}
}
线程的联合
线程A在中可以调用线程B的join()方法,让线程B和线程A联合,线程A必须等待线程B执行完毕后,才能继续执行。
join方法的使用
指调用该方法的线程在执行完run()方法后,在执行join 方法后面的代码,即将两个线程合并,用于实现同步控制。
class A implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class JoinThread {
public static void main(String[] args) {
Thread t = new Thread(new A());
Thread t1 = new Thread(new B());
t.start();
t1.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+""+i);
if(i ==2 ){
try {
t.join();//先执行完线程A和线程B,在执行线程main
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程联合案例
/**
* 儿子买烟线程
*/
class SonThread implements Runnable{
@Override
public void run() {
System.out.println("儿子出门买烟");
System.out.println("儿子买烟需要 10 分钟");
for(int i=0;i<10;i++){
System.out.println("第"+i+"分钟");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("儿子买烟回来了");
}
}
/**
* 爸爸抽烟线程
*/
class FatherThread implements Runnable{
@Override
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了");
System.out.println("爸爸让儿子去买一包利群");
Thread t = new Thread(new SonThread());
t.start();
System.out.println("等待儿子买烟回来");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门找儿子");
System.exit(1);
}
System.out.println("爸爸高兴的接过烟,并把零钱给了儿子");
}
}
public class JoinDemo {
public static void main(String[] args) {
System.out.println("爸爸和儿子买烟的故事");
Thread t = new Thread(new FatherThread());
t.start();
}
}
获取当前线程名称
方式一
this.getName()获取线程名称,该方法适用于继承 Thread 实现多线程方式
class GetName1 extends Thread{
@Override
public void run() {
System.out.println(this.getName());
}
}
方式二
Thread.currentThread().getName()获取线程名称,该方法适用于实现Runnable 接口实现多线程方式。
class GetName2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
设置线程名称
方式一
通过构造方法设置线程名称。
class SetName1 extends Thread{
public SetName1(String name){
super(name);
}
@Override
public void run() {
System.out.println(this.getName());
}
}
public class SetNameThread {
public static void main(String[] args) {
SetName1 setName1 = new SetName1("SetName1");
setName1.start();
}
}
方式二
通过 setName()方法设置线程名称。
class SetName2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class SetNameThread {
public static void main(String[] args) {
Thread thread = new Thread(new SetName2());
thread.setName("SetName2");
thread.start();
}
}
判断当前线程是否存活
isAlive()方法: 判断当前的线程是否处于活动状态。返回布尔类型的值。
活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。
class Alive implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().isAlive()+" 2");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class AliveThread {
public static void main(String[] args) {
Thread thread = new Thread(new Alive());
System.out.println(thread.isAlive()+" 1");
thread.start();
System.out.println(thread.isAlive()+" 3");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.isAlive()+" 4");
}
}
线程优先级
Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关。
注意:线程的优先级,不是说哪个线程优先执行,如果设置某个线程的优先级高。那就是有可能被执行的概率高。并不是优先执行。
线程优先级的使用
使用下列方法获得或设置线程对象的优先级:
int getPriority();
void setPriority(int newPriority);
class Priority implements Runnable{
private int num = 0;
private boolean flag = true;
@Override
public void run() {
while(this.flag){
System.out.println(Thread.currentThread().getName()+""+num++);
}
}
public void stop(){
this.flag = false;
}
}
public class PriorityThread {
public static void main(String[] args)throws Exception {
Priority p1 = new Priority();
Priority p2 = new Priority();
Thread t1 = new Thread(p1,"线程 1");
Thread t2 = new Thread(p2,"线程 2");
System.out.println(t1.getPriority());
//Thread.MAX_PRIORITY = 10
t1.setPriority(Thread.MAX_PRIORITY);
//Thread.MIN_PRIORITY = 1
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
Thread.sleep(1000);
p1.stop();
p2.stop();
}
}
守护线程
在 Java 中有两类线程:
User Thread(用户线程):就是应用程序里的自定义线程。
Daemon Thread(守护线程):比如垃圾回收线程,就是最典型的守护线程。
守护线程是一个服务线程,服务其他的线程,其他线程只有一种,就是用户线程。
特点:
随着用户线程死亡而死亡。
守护线程与用户线程区别
用户线程只有两种情况会死掉:
run中异常终止
run执行完毕,线程死亡
守护线程,随着用户线程的死亡而死亡。
守护线程使用
/**
* 守护线程类
*/
class Daemon implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class UsersThread implements Runnable{
@Override
public void run() {
Thread t = new Thread(new Daemon(),"Daemon");
//将该线程设置为守护线程,两个线程一起执行,用户线程执行结束两个一起结束。
t.setDaemon(true);
t.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DaemonThread {
public static void main(String[] args)throws Exception {
Thread t = new Thread(new UsersThread(),"UsersThread");
t.start();
Thread.sleep(1000);
System.out.println("主线程结束");
}
}
线程同步
线程冲突现象
线程同步概念
多个线程访问同一对象,某些线程还想修改这个对象。此时需要线程同步。本质是一个等待机制,多个访问同一对象的线程进入等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
线程冲突案例演示
银行取钱的基本流程基本上可以分为如下几个步骤。
(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。
(2)用户输入取款金额
(3)系统判断账户余额是否大于取款金额
(4)如果余额大于取款金额,则取钱成功;如果余额小于取款金额,则取钱失败。
/**
* 账户类
*/
class Account{
//账号
private String accountNo;
//账户的余额
private double balance;
public Account() {
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/**
* 取款线程
*/
class DrawThread extends Thread{
//账户对象
private Account account;
//取款金额
private double drawMoney;
public DrawThread(String name,Account account,double drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
/**
* 取款线程
*/
@Override
public void run() {
//判断当前账户余额是否大于或等于取款金额
if(this.account.getBalance() >= this.drawMoney){
System.out.println(this.getName()+" 取钱成功!吐出钞票:"+this.drawMoney);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}//更新账户余额
this.account.setBalance(this.account.getBalance()-
this.drawMoney);
System.out.println(" 余额为: "+this.account.getBalance());
}else{
System.out.println(this.getName()+" 取钱失败,余额不足");}
}
}
public class DrawMoneyThread {
public static void main(String[] args) {
Account account = new Account("1234",1000);
new DrawThread("老公",account,800).start();
new DrawThread("老婆",account,800).start();
}
}
实现线程同步
使用synchronized 关键字避免同一个数据对象被多个线程同时访问造成的这种问题。
synchronized 语法结构:
synchronized(锁对象){
同步代码
}
需要考虑的问题:
需要对那部分的代码在执行时具有线程互斥的能力(线程互斥:并行变串行)。
需要对哪些线程中的代码具有互斥能力(通过 synchronized 锁对象来决定)。
两种用法:
synchronized 方法
通过在方法声明中加入 synchronized 关键字来声明,语法如下:
public synchronized void accessVal(int newVal);
synchronized 在方法声明时使用:放在范围操作符(public)之后,返回类型声明(void)之前。这时同一个对象下 synchronized 方法在多线程中执行时,该方法是同步的,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,就算对象下有多个synchronized方法,也要等一个线程执行完指定的synchronized方法后别的线程才能调用当前对象下的synchronized方法。
synchronized 块
synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
修改线程冲突案例演示
/**
* 账户类
*/
class Account{
//账号
private String accountNo;
//账户的余额
private double balance;
public Account() {
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/**
* 取款线程
*/
class DrawThread extends Thread{
//账户对象
private Account account;
//取款金额
private double drawMoney;
public DrawThread(String name,Account account,double drawMoney){
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
/**
* 取款线程
*/
@Override
public void run() {
synchronized (this.account) {
//判断当前账户余额是否大于或等于取款金额
if (this.account.getBalance() >= this.drawMoney) {
System.out.println(this.getName() + " 取钱成功!吐出钞票:" + this.drawMoney);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
this.account.setBalance(this.account.getBalance()-this.drawMoney);
System.out.println(" 余额为:" +
this.account.getBalance());
} else {
System.out.println(this.getName() + " 取钱失败,余额不足");
}
}
}
}
public class DrawMoneyThread {
public static void main(String[] args) {
Account account = new Account("1234",1000);
new DrawThread("老公",account,800).start();
new DrawThread("老婆",account,800).start();
}
}
线程同步使用
使用this 作为线程对象锁
语法结构:
synchronized(this){
//同步代码
}
或
public synchronized void accessVal(int newVal){
//同步代码
}
/**
* 定义程序员类
*/
class Programmer{
private String name;
public Programmer(String name){
this.name = name;
}
/**
* 打开电脑
*/
public synchronized void computer(){
try {
System.out.println(this.name + " 接通电源");
Thread.sleep(500);
System.out.println(this.name + " 按开机按键");
Thread.sleep(500);
System.out.println(this.name + " 系统启动中");
Thread.sleep(500);
System.out.println(this.name + " 系统启动成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 编码
*/
public synchronized void coding(){
try {
System.out.println(this.name + " 双击Idea");
Thread.sleep(500);
System.out.println(this.name + " Idea 启动完毕");
Thread.sleep(500);
System.out.println(this.name + " 开开心心的写代码");} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 打开电脑的工作线程
*/
class Working1 extends Thread{
private Programmer p;
public Working1(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.computer();
}
}
/**
* 编写代码的工作线程
*/
class Working2 extends Thread{
private Programmer p;
public Working2(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.coding();
}
}
public class test {
public static void main(String[] args) {
Programmer p = new Programmer("张三");
new Working1(p).start();
new Working2(p).start();
}
}
使用字符串作为线程对象锁
语法结构:
synchronized(“字符串”){
//同步代码
}
/**
* 定义程序员类
*/
class Programmer{
private String name;
public Programmer(String name){
this.name = name;
}
/**
* 打开电脑
*/
synchronized public void computer(){
try {
System.out.println(this.name + " 接通电源");
Thread.sleep(500);
System.out.println(this.name + " 按开机按键");
Thread.sleep(500);
System.out.println(this.name + " 系统启动中");
Thread.sleep(500);
System.out.println(this.name + " 系统启动成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 编码
*/
synchronized public void coding(){
try {
System.out.println(this.name + " 双击Idea");
Thread.sleep(500);
System.out.println(this.name + " Idea 启动完毕");
Thread.sleep(500);
System.out.println(this.name + " 开开心心的写代码");} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 去卫生间
*/
public void wc(){
synchronized ("suibian") {
try {
System.out.println(this.name + " 打开卫生间门");
Thread.sleep(500);
System.out.println(this.name + " 开始排泄");
Thread.sleep(500);
System.out.println(this.name + " 冲水");
Thread.sleep(500);
System.out.println(this.name + " 离开卫生间");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 打开电脑的工作线程
*/
class Working1 extends Thread{
private Programmer p;
public Working1(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.computer();
}
}
/**
* 编写代码的工作线程
*/
class Working2 extends Thread{
private Programmer p;
public Working2(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.coding();
}
}
/**
* 去卫生间的线程
*/
class WC extends Thread{
private Programmer p;
public WC(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.wc();
}
}
public class TestSyncThread {
public static void main(String[] args) {
/*Programmer p = new Programmer("张三");
new Working1(p).start();
new Working2(p).start();*/
Programmer p = new Programmer("张三");
Programmer p1 = new Programmer("李四");
Programmer p2 = new Programmer("王五");
new WC(p).start();
new WC(p1).start();
new WC(p2).start();
}
}
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
案例演示:
/**
* 口红类
*/
class Lipstick{
}
/**
* 镜子类
*/
class Mirror{
}
/**
* 化妆线程类
*/
class Makeup extends Thread {
private int flag; //flag=0:拿着口红。flag!=0:拿着镜子private String girlName;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
public void setFlag(int flag) {
this.flag = flag;
}
public void setGirlName(String girlName) {
this.girlName = girlName;
}
@Override
public void run() {
this.doMakeup();
}
/**
* 开始化妆
*/
public void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + " 拿着口红");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(this.girlName + " 拿着镜子");
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + " 拿着镜子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(this.girlName + " 拿着口红");
}
}
}
}
}
public class DeadLockThread {
public static void main(String[] args) {
Makeup makeup = new Makeup();
makeup.setFlag(0);
makeup.setGirlName("大丫");
Makeup makeup1 = new Makeup();
makeup1.setFlag(1);
makeup1.setGirlName("小丫");
makeup.start();
makeup1.start();
}
}
死锁问题的解决
同一个代码块,不要同时持有两个对象锁。
/**
* 口红类
*/
class Lipstick{
}
/**
* 镜子类
*/
class Mirror{
}
/**
* 化妆线程类
*/
class Makeup extends Thread{
private int flag; //flag=0:拿着口红。flag!=0:拿着镜子private String girlName;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
public void setFlag(int flag) {
this.flag = flag;
}
public void setGirlName(String girlName) {
this.girlName = girlName;
}
@Override
public void run() {
this.doMakeup();
}
/**
* 开始化妆
*/
public void doMakeup(){
if(flag == 0){
synchronized (lipstick){
System.out.println(this.girlName+" 拿着口红");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror){
System.out.println(this.girlName+" 拿着镜子");
}
}else{
synchronized (mirror){
System.out.println(this.girlName+" 拿着镜子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick){
System.out.println(this.girlName+" 拿着口红");
}
}
}
}
public class DeadLockThread {
public static void main(String[] args) {
Makeup makeup = new Makeup();
makeup.setFlag(0);
makeup.setGirlName("大丫");
Makeup makeup1 = new Makeup();
makeup1.setFlag(1);
makeup1.setGirlName("小丫");
makeup.start();
makeup1.start();
}
}
线程并发协作(生产者/消费者模式)
角色介绍
生产者
生产者指的是负责生产数据的模块。
消费者
消费者指的是负责处理数据的模块。
缓冲区
消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
缓冲区是实现并发的核心,缓冲区的设置有两个好处:
实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离,解除了生产者与消费者之间的耦合。
解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。
实现生产者与消费者模式
创建缓冲区
/**
* 定义馒头类
*/
class ManTou{
private int id;
public ManTou(int id){
this.id = id;
}
public int getId(){
return this.id;
}
}
/**
* 定义缓冲区类
*/
class SyncStack{
//定义存放馒头的盒子
private ManTou[] mt = new ManTou[10];
//定义操作盒子的索引
private int index;
/**
* 放馒头
*/
public synchronized void push(ManTou manTou){
//判断盒子是否已满
while(this.index == this.mt.length){
try {
/**
* 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒取馒头的线程
/**
* 语法:该方法必须要在 synchronized 块中调用。
* 该方法会唤醒处于等待状态队列中的一个线程。
*/
this.notify();
this.mt[this.index] = manTou;
this.index++;
}
/**
* 取馒头
*/
public synchronized ManTou pop(){
while(this.index == 0){
try {
/**
* 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
this.index--;
return this.mt[this.index];
}
}
创建生产者消费者线程
/**
* 定义馒头类
*/
class ManTou{
private int id;
public ManTou(int id){
this.id = id;
}
public int getId(){
return this.id;
}
}
/**
* 定义缓冲区类
*/
class SyncStack{
//定义存放馒头的盒子
private ManTou[] mt = new ManTou[10];
//定义操作盒子的索引
private int index;
/**
* 放馒头
*/
public synchronized void push(ManTou manTou){
//判断盒子是否已满
while(this.index == this.mt.length){
try {
/**
* 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
this.wait();//wait 是等待另一个线程 notify 后, 才继续执行, 不用等另一个线程全部执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒取馒头的线程
/**
* 语法:该方法必须要在 synchronized 块中调用。
* 该方法会唤醒处于等待状态队列中的一个线程。
*/
System.out.println("检测是否会继续执行一次");
this.notify();//因为生产者进程有锁,所以在生产者线程未执行wait前消费者线程无法调用另一个加锁的方法,notify生效后消费者线程开始执行,在此处执行是为了确保index的值正确
this.mt[this.index] = manTou;
this.index++;
}
/**
* 取馒头
*/
public synchronized ManTou pop(){
while(this.index == 0){
try {
/**
* 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
this.index--;
return this.mt[this.index];
}
}
/**
* 定义生产者线程类
*/
class ShengChan extends Thread{
private SyncStack ss;
public ShengChan(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("生产馒头:"+i);
ManTou manTou = new ManTou(i);
this.ss.push(manTou);
}
}
}
/**
* 定义消费者线程类
*/
class XiaoFei extends Thread{
private SyncStack ss;
public XiaoFei(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for(int i=0;i<10;i++){
ManTou manTou = this.ss.pop();
System.out.println("消费馒头:"+i);
}
}
}
public class test {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
new ShengChan(ss).start();
new XiaoFei(ss).start();
}
}
线程并发协作总结
线程并发协作(也叫线程通信)。
生产者消费者模式:
生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
在生产者消费者问题中,仅有 synchronized 是不够的。synchronized 可阻止并发更新同一个共享资源,实现了同步但是synchronized不能用来实现不同线程之间的消息传递(通信)。
那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
6.以上方法均是 java.lang.Object 类的方法;
都只能在同步方法或者同步代码块中使用,否则会抛出异常。
暂无评论内容