智渔课堂官方免费教程四十 :Java基础教程之线程同步

时间:2022-01-18 23:33:35

线程的同步

指当多个线程使用同一对象中被同步的资源时,要根据“先来后到”的顺序使用。
举个例子:现在只有一台电脑,现在有两个人A和B想玩游戏,一个人C想写代码,一个人D想听音乐。此时A、B、C三个人要抢这台电脑,谁先抢到谁用,用完了后面两个人在接着抢,谁抢到谁用。而D则不用,在另外三个人中任意一个人正在使用的时候,都可以播放音乐给他听;由此可以看出玩游戏和写代码的功能(方法)是要有“先来后到”的顺序的,而听音乐这个功能不需要。所以玩游戏和写代码的方法就是需要被同步的,而听音乐就不需要同步
同步使用关键字synchronized实现
同步方法:[访问修饰符]  synchronized  返回值类型  方法名(){}
同步块:synchronized(对象) { //代码块 }
同步块和前面所提到的代码块一样,只不过这块代码是被同步的。被同步的部分也按照“先来后到”的顺序执行
在开发的过程中,应当尽量缩小同步代码的范围,因为多个线程执行时要有顺序的执行,这样会大大降低程序的运行效率


以下实例代码中创建了一个Computer类中含有一个非同步的方法listenMusic,一个含有同步块的非同步方法printer和两个同步方法playGame和coding。
七个线程类ThreadA、ThreadB、ThreadC、ThreadD、ThreadE、ThreadF、ThreadG;其中ThreadA和ThreadB中都执行了非同步的方法listenMusic;ThreadC和ThreadD中都执行了同步方法playGame;ThreadE中执行了同步方法coding;ThreadF中ThreadG都执行了含有同步块的非同步方法printer;
五个测试类TestAB、TestBC、TestCD、TestDE、TestEF、TestFG;根据测试类的类名最后两个字母,测试对象的两个线程;例如TestAB测试ThreadA和ThreadB
实例:
package thread.synchronize;
/**
* 创建Computer类
* 其中包含同步方法,非同步方法和含有同步块的非同步方法
* @author 学霸联盟 - 赵灿
*/
public class Computer {
/**
* 非同步方法;功能:听音乐
* @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
*/
public void listenMusic(String threadTag) {
System.out.println(threadTag + "-听音乐开始");
try {
//此处休眠1秒是为了模拟编写代码消耗的时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadTag + "-听音乐结束");
}
/**
* 含有同步块的非同步方法;功能:打印材料
* @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
*/
public void printer(String threadTag) {
System.out.println(threadTag + "-准备材料开始");
try {
//此处休眠1秒是为了模拟准备材料消耗的时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadTag + "-准备材料结束");
//同步块:获得当前对象的锁
synchronized(this){
System.out.println(threadTag + "-打印材料开始");
try {
//此处休眠1秒是为了模拟打印材料消耗的时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadTag + "-打印材料结束");
}
}
/**
* 同步方法;功能:玩游戏
* @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
*/
public synchronized void playGame(String threadTag) {
System.out.println(threadTag + "-玩游戏开始");
try {
//此处休眠3秒是为了模拟玩游戏消耗的时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadTag + "-玩游戏结束");
}
/**
* 同步方法;功能:编写代码
* @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
*/
public synchronized void coding(String threadTag) {
System.out.println(threadTag + "-编写代码开始");
try {
//此处休眠3秒是为了模拟编写代码消耗的时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadTag + "-编写代码结束");
}
}


package thread.synchronize;
/**
* 创建ThreadA类
* 用于执行Computer对象中的非同步方法listenMusic
* @author 学霸联盟 - 赵灿
*/
public class ThreadA extends Thread {
/*
* 声明成员变量pc
* 目的是因为在run方法中需要使用外部传进来的Computer对象
* 但run方法中无法使用构造方法中的局部变量localPC
* 所以此处声明一个成员变量pc用于接收外部传入的对象
* 这样以来run方法中,便可以通过成员变量pc可以使用外部传入的对象了
*/
private Computer pc;
/*
* 此处的localPC会接收到外部传入的Computer类型的对象
* 并将其传递给成员变量pc
*/
public ThreadA(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//使用pc完成听音乐的更能,即使用pc调用listenMusic方法
pc.listenMusic("ThreadA");
}
}


package thread.synchronize;
/**
* 创建ThreadB类:作用和代码同ThreadA
* @author 学霸联盟 - 赵灿
*/
public class ThreadB extends Thread {
private Computer pc;
public ThreadB(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
pc.listenMusic("ThreadB");
}
}


package thread.synchronize;
/**
* 创建测试类TestAB
* 用于测试多个线程同时调用同一对象的同一个非同步方法
* 结果将是多个线程同时执行非同步方法
* @author 学霸联盟 - 赵灿
*/
public class TestAB {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadA和ThreadB中都执行了pc的非同步方法listenMusic
ThreadA ta = new ThreadA(pc);
ThreadB tb = new ThreadB(pc);
//启动线程ta和tb
ta.start();
tb.start();
}
}
运行结果:
ThreadA-听音乐开始
ThreadB-听音乐开始
ThreadB-听音乐结束
ThreadA-听音乐结束
package thread.synchronize;
/**
* 创建ThreadC类:代码基本和ThreadA相同
* @author 学霸联盟 - 赵灿
*/
public class ThreadC extends Thread {
private Computer pc;
public ThreadC(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//调用pc的同步方法playGame
pc.playGame("ThreadC");
}
}

package thread.synchronize;
/**
* 创建测试类TestBC
* 用于测试两个线程同时调用同一对象的非同步方法和同步方法
* 结果是执行非同步方法和同步方法的两个线程可以同时执行,互不影响
* @author 学霸联盟 - 赵灿
*/
public class TestBC {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadB执行了pc的非同步方法listenMusic
ThreadB tb = new ThreadB(pc);
//ThreadC执行了pc的同步方法playGame
ThreadC tc = new ThreadC(pc);
tb.start();
tc.start();
}
}
运行结果:
ThreadC-玩游戏开始
ThreadB-听音乐开始
ThreadB-听音乐结束
ThreadC-玩游戏结束
package thread.synchronize;
/**
* 创建ThreadD类:代码和功能同ThreadC
* @author 学霸联盟 - 赵灿
*/
public class ThreadD extends Thread {
private Computer pc;
public ThreadD(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//调用pc的同步方法playGame
pc.playGame("ThreadD");
}
}

package thread.synchronize;
/**
* 创建测试类TestCD
* 用于测试多个线程同时调用同一对象的同一个同步方法
* 结果是某一线程先获得对象的锁的执行完后,第二个线程才能执行
* @author 学霸联盟 - 赵灿
*/
public class TestCD {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadC和ThreadD中都执行了pc的同步方法playGame
ThreadC tc = new ThreadC(pc);
ThreadD td = new ThreadD(pc);
//启动线程tc和td
tc.start();
td.start();
}
}
运行结果:
ThreadC-玩游戏开始
ThreadC-玩游戏结束
ThreadD-玩游戏开始
ThreadD-玩游戏结束
package thread.synchronize;
/**
* 创建ThreadE类
* @author 学霸联盟 - 赵灿
*/
public class ThreadE extends Thread {
private Computer pc;
public ThreadE(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//调用pc的同步方法coding
pc.coding("ThreadE");
}
}

package thread.synchronize;
/**
* 创建测试类TestDE
* 用于测试多个线程同时调用同一对象的不同的同步方法
* 结果也是某一线程先获得对象的锁的执行完后,第二个线程才能执行
* @author 学霸联盟 - 赵灿
*/
public class TestDE {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadD执行了pc的同步方法playGame
ThreadD td = new ThreadD(pc);
//ThreadE中执行了pc的同步方法coding
ThreadE te = new ThreadE(pc);
td.start();
te.start();
}
}
运行结果:
ThreadD-玩游戏开始
ThreadD-玩游戏结束
ThreadE-编写代码开始
ThreadE-编写代码结束
package thread.synchronize;
/**
* 创建ThreadF类
* @author 学霸联盟 - 赵灿
*/
public class ThreadF extends Thread {
private Computer pc;
public ThreadF(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//调用pc的含同步块的方法printer
pc.printer("ThreadF");
}
}

package thread.synchronize;
/**
* 创建测试类TestEF
* 用于测试多个线程同时调用同一对象的同步方法和含有同步块的非同步方法
* 结果将是被同步的部分有先后,非同步部分代码同时执行
* @author 学霸联盟 - 赵灿
*/
public class TestEF {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadE中执行了pc的同步方法coding
ThreadE te = new ThreadE(pc);
//ThreadF执行了pc的含同步块的非同步方法printer
ThreadF tf = new ThreadF(pc);
te.start();
tf.start();
}
}
运行结果:
ThreadF-准备材料开始
ThreadE-编写代码开始
ThreadF-准备材料结束
ThreadE-编写代码结束
ThreadF-打印材料开始
ThreadF-打印材料结束
package thread.synchronize;
/**
* 创建ThreadG类
* @author 学霸联盟 - 赵灿
*/
public class ThreadG extends Thread {
private Computer pc;
public ThreadG(Computer localPC) {
pc = localPC;
}
@Override
public void run() {
//调用pc的含同步块的方法printer
pc.printer("ThreadG");
}
}
package thread.synchronize;

/**
* 创建测试类TestFG
* 用于测试多个线程同时调用同一对象中同一个含有同步块的非同步方法
* 结果将是被同步的部分有先后,非同步部分代码同时执行
* @author 学霸联盟 - 赵灿
*/
public class TestFG {
public static void main(String[] args) {
/**
* 为了保证只有一台电脑(即同一个对象)供多个线程访问
* 所以只在此处创建一个Computer对象作为实参传递给各个线程
*/
Computer pc = new Computer();
//ThreadF和ThreadG都执行了pc的含同步块的非同步方法printer
ThreadF tf = new ThreadF(pc);
ThreadG tg = new ThreadG(pc);
tf.start();
tg.start();
}
}
运行结果:
ThreadF-准备材料开始
ThreadG-准备材料开始
ThreadG-准备材料结束
ThreadG-打印材料开始
ThreadF-准备材料结束
ThreadG-打印材料结束
ThreadF-打印材料开始
ThreadF-打印材料结束

wait方法和notify方法/notifyAll方法

wait([int][,int]); Object类中的方法,在线程A中使用对象o调用wait方法;会阻塞线程A;
如果没有传入时间参数,直到在其他地方调用对象o的notify/notifyAll时才会恢复;
如果传入long类型的时间参数,指定时间结束后自动恢复
而且线程A必须先获得对象o的锁,否则会出现异常。
实例:
package thread.wait;
/**
* 创建WaitDemo类
* 用于测试wait、notify、notifyAll方法
* @author 学霸联盟 - 赵灿
*/
public class WaitDemo {
public static void main(String[] args) {
/*
* 在这里创建一个唯一的Object对象onlyObject
* 用于传递给以下三个线程使用,是为了保证三个线程使用的是同一对象
*/
Object onlyObject = new Object();
/*
* 创建三个线程对象ta、tb、tc
* onlyObject会被赋值给每个线程的构造方法中的变量localObject
* 相当于localObject = onlyObject;
*/
ThreadA ta = new ThreadA(onlyObject);
ThreadB tb = new ThreadB(onlyObject);
ThreadC tc = new ThreadC(onlyObject);
//先启动线程ta和tb
ta.start();
tb.start();
try {
/*
* 此处执行Thread.sleep(100);会是主线程休眠100毫秒
* 100毫秒后才会执行tc.start();启动线程tc
* 目的是为了保证线程ta和tb中的wait方法先执行,阻塞住线程ta和tb
* 然后在执行线程tc中的notify()方法是唤醒操作
* 否则如果先执行了唤醒操作,而后执行阻塞操作,将看不到唤醒的效果
*/
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动线程tc
tc.start();
}
}


/**
* 创建ThreadA类
* 用于执行wait方法;阻塞操作
* @author 学霸联盟 - 赵灿
*/
class ThreadA extends Thread {
/*
* 声明成员变量obj
* 目的是因为在run方法中需要使用外部传进来的Object对象
* 但run方法中无法使用构造方法中的局部变量localObject
* 所以此处声明一个成员变量obj用于接收外部传入的对象
* 这样以来run方法中,便可以通过成员变量obj可以使用外部传入的对象了
*/
private Object obj;
/*
* 此处的localObject会接收到外部传入的Object类型的对象
* 并将其传递给成员变量obj
*/
public ThreadA(Object localObject) {
//赋值给成员变量obj
this.obj = localObject;
}

@Override
public void run() {
System.out.println("ThreadA--执行开始");
/*
* 调用wait方法时,必须使用同步的方式获取调用wait方法对象的锁
* 否则会出现IllegalMonitorStateException异常
* 本例中,此处使用的是obj调用wait(obj.wait())
* 所以同步的也应该是obj(synchronized(obj) )
*/
synchronized (obj) {
try {
System.out.println("ThreadA--被阻塞");
/*
* 调用对象obj的wait方法,阻塞的将是执行这句代码的线程
* 本例中是执行此处代码的是线程ta,所以被阻塞的就是线程ta
*/
obj.wait();
System.out.println("ThreadA--被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ThreadA--执行结束");
}
}


/**
* 创建ThreadB类
* 用于执行wait方法;阻塞操作
* 其中的代码和ThreadA相同,就不再一一写注释了
* @author 学霸联盟 - 赵灿
*/
class ThreadB extends Thread {
private Object obj;
public ThreadB(Object localObject) {
this.obj = localObject;
}
@Override
public void run() {
synchronized (obj) {
System.out.println("ThreadB--执行开始");
synchronized (obj) {
try {
System.out.println("ThreadB--被阻塞");
/*
* 执行obj.wait(),阻塞的将是执行这句代码的线程
* 本例中是执行此处代码的是线程tb,所以被阻塞就是线程tb
*/
obj.wait();
System.out.println("ThreadB--被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ThreadB--执行结束");
}
}
}


/**
* 创建ThreadC类
* 用于执行notify方法;唤醒操作
* @author 学霸联盟 - 赵灿
*/
class ThreadC extends Thread {
private Object obj;

public ThreadC(Object localObject) {
this.obj = localObject;
}

@Override
public void run() {
/*
* 调用notify方法时,必须使用同步的方式获取调用notify方法对象的锁
* 否则会出现IllegalMonitorStateException异常
* 本例中,此处使用的是obj调用notify(obj.notify())
* 所以同步的也应该是obj(synchronized(obj) )
*/
synchronized (obj) {
System.out.println("ThreadC--唤醒最先被obj对象阻塞的线程");
/*
* 执行obj.notify();将唤醒第一个被obj阻塞的线程
* 如果执行obj.notifyAll();所有被obj阻塞的线程都会被唤醒
*/
obj.notify();
}
}
}
运行结果:
ThreadA--执行开始
ThreadB--执行开始
ThreadB--被阻塞
ThreadA--被阻塞
ThreadC--唤醒最先被obj对象阻塞的线程
ThreadB--被唤醒
ThreadB--执行结束

总结:线程同步是为了在多线程的情况下保护数据在操作过程中的安全性,但是这样做极大的影响了软件的执行效率,所以在使用同步的过程中,应尽可能的缩小同步范围,从而减少同步对性能的影响。