线程的同步
线程的安全问题
- 多个线程执行的不确定性引起执行的结果的不稳定性
- 多个线程对数据的共享,会造成操作的不完整性、会破坏数据(例如窗口买票问题,多个窗口对票数进行共享,会出现两个窗口卖号码相同的票给不同的人)
通过同步机制解决线程安全问题
方法一:同步代码块
格式
synchronized(同步监视器){
需要被同步的代码
}
举例说明
class Thread implements Runnable{
private Object obj = new Object();
public void run() {
//使用类对象充当锁
synchronized(obj){
.......
}
}
}
说明
- 操作共享数据的代码即为需要被同步的代码
- 不能多包含代码,也不能少包含代码
- 共享数据:多个线程共同操作的变量
- 同步监视器:俗称锁
- 任何一个类的对象都可以来充当锁
- 要求多个线程必须共用同一把锁
- 在实现Runnable接口创建多线程的方式中,考虑使用this充当同步监视器
- 在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,考虑使用当前类来充当同步监视器
特点
- 好处:解决线程的安全问题
- 局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于一个单线程的过程,效率低
代码实现
实现Runnable接口创建多线程的方式
/**
* 创建三个窗口买票,票数100张:使用实现Runnable接口的方式实现的
*/
class WindowThread implements Runnable{
private int ticket = 100;
// private Object obj = new Object();
public void run() {
while (true) {
//此时this:唯一的WindowThread对象
synchronized(this){// 方式二:synchronized(obj){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Test1 {
public static void main(String[] args) {
WindowThread window = new WindowThread();
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
Thread w3 = new Thread(window);
w1.setName("窗口1");
w1.start();
w2.setName("窗口2");
w2.start();
w3.setName("窗口3");
w3.start();
}
}
继承Thread类创建多线程的方式
class Window extends Thread {
// 大家公用数据,只有100张票
private static int ticket = 100;
private static Object obj = new Object();
public void run() {
while (true) {
//方式二
synchronized(Window.class){
// 方式一:synchronized(obj){
//synchronized(this)错误的,此时this代表着三个对象
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + "买票,票号为" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Test2 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
同步方法
如果操作共享数据的代码完整的声明在一个方法中,就可以将此方法声明同步的
格式
利用synchronized 修饰方法
public synchronized void XXX(){
}
或
public static synchronized void XXX(){
}
说明
- synchronized修饰方法时锁定的是调用该方法的对象
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,同步监视器是this
- 静态的同步方法,同步监视器是当前类本身(Window.class)
代码实现
实现Runnable接口创建多线程的方式
非静态同步方法,调用this
class WindowThread3 implements Runnable{
private int ticket = 100;
private static boolean isFlag = true;
// private Object obj = new Object();
public void run() {
while (isFlag) {
show();
}
}
public synchronized void show(){//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
ticket--;
}else{
isFlag = false;
}
}
}
public class Test3 {
public static void main(String[] args) {
WindowThread3 window = new WindowThread3();
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
Thread w3 = new Thread(window);
w1.setName("窗口1");
w1.start();
w2.setName("窗口2");
w2.start();
w3.setName("窗口3");
w3.start();
}
}
继承Thread类创建多线程的方式
静态同步方法,调用当前类本身
class Window4 extends Thread{
private static int ticket = 100;
private static boolean isFlag = true;
@Override
public void run() {
while(isFlag){
show();
}
}
public static synchronized void show(){
//同步监视器:Window.class
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
ticket--;
}else{
isFlag = false;
}
}
}
public class Test4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
通过Lock(锁)解决线程安全问题
步骤
-
实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
- true代表公平
- 不填默认为false
-
调用锁的方法
在可能会出现安全问题代码前调用Lock接口中的方法Lock获取锁
lock.lock(); -
调用解锁的方法
lock.unlock();
注意:其中调用lock()方法和unlock()方法时要用try()finally()包住
代码实现
class Window5 implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
public void run() {
while (true) {
try{
//2.调用锁定的方法:lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票" + ":" + "票号为" + ticket);
ticket--;
}else{
break;
}
}finally{
//3.调用解锁的方法:unlock()
lock.unlock();
}
}
}
}
public class Test5 {
public static void main(String[] args) {
Window5 window = new Window5();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
t1.start();
t2.start();
t3.start();
}
}
synchronized和Lock的异同
异
- synchronized机制在执行完相应的代码逻辑后自动释放同步监视器
- Lock需要手动的启动同步(lock),同时结束同步也需要手动的实现(unlock)
同
- 都可以解决线程安全问题
释放锁与不释放锁的操作
释放锁的操作
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中遇到了break、return终止了该代码块、方法的继续执行
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不释放锁的操作
- 线程在执行同步代码块或同步方法时,程序调用了Thread.sleep()或Thread.yield()方法暂停当前线程的执行
- 线程在执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
- 尽量避免使用suspend()(挂起)和resume()(继续执行)来控制线程
使用顺序
Lock--->同步代码块--->同步方法
死锁
- 不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 使用同步时,避免出现死锁
- 避免
- 专门的算法
- 尽量减少同步资源的定义
- 尽量避免嵌套同步