说在前面:
1.什么是线程同步?
答: 处理多线程问题时,多个线程访问同一个对象,并且某个对象还想修改这个线程。这时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
2.为什么有线程同步机制(目的)?
答: 解决线程安全问题
3.什么是线程安全问题?
答: 所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的
4.怎么解决线程安全问题?
方式一:同步代码块
方式二:同步方法
方式三:Lock锁
引入一个例子来利用线程的同步机制解决线程的安全问题:
创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
方式一:同步代码块
代码格式:
synchronized(同步监视器){
//需要被同步的代码
}
同步监视器:也叫锁,任何一个类的对象都可以充当锁.要求多个线程必须要使用同一把锁.
共享数据:多个线程共同操作的变量,比如上面例子的ticket即为共享数据
代码示例:
//使用实现Runnable接口的方式 创建三个窗口卖100张票
//出现线程安全问题,利用同步代码块的方式解决
public class Window01 implements Runnable {
private int ticket = 100;//总票数100张
@Override
public void run() {
while (true) {
//需要同步的代码块
synchronized (this) {
//这里用this充当同步监视器,也可以重新new一个对象比如Object obj = new Object();把obj当作锁
//因为this表示当前类的对象在本类中只创建了一个窗口对象w,是唯一的,符合多个线程共用一把锁;
if (ticket > 0) {
try {
Thread.sleep(50);//让线程休眠一会,出票慢一点
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-"+ticket);
ticket--;
} else {
break;
}
}
}
}
}
//测试类:
class test {
public static void main(String[] args) {
//创建一个窗口对象
Window01 w = new Window01();
//创建三个线程共享同一个窗口对象相当于三个窗口在同时卖票
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("Window01");
t2.setName("Window02");
t3.setName("Window03");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。也就是同步方法.
-
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
还是上面的例子,代码示例:
public class Window01 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
Ticket();
}
}
//同步方法:
public synchronized void Ticket() {//当前类的对象Window01只有一个对象即w (同步监视器this)
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-" + ticket);
ticket--;
}
}
}
方式三:Lock锁
还是卖票问题,直接上代码:
import java.util.concurrent.locks.ReentrantLock;
//Lock锁的方式解决线程安全问题:
public class Window03 implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock的对象
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//2.把操作共享数据的代码放在try-finally中,因为这些代码可能会出现异常
try {
//3.用上面实例化ReentrantLock的对象调用锁定方法lock();
lock.lock();
//锁住后下面的的代码都是线程安全的
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
} finally {
//4.调用解锁方法unlock();
lock.unlock();
}
}
}
}
//测试类:
class test {
public static void main(String[] args) {
//创建窗口对象
Window03 w = new Window03();
//创建三个线程共用同一个窗口对象
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("Window01");
t2.setName("Window02");
t3.setName("Window03");
t1.start();
t2.start();
t3.start();
}
}
synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;
Lock需要手动的启动同步lock(),同时结束同步也需要手动的实现unlock();