当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。此时,多线程的安全问题就会出现。
解决办法是对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行。
通过模拟3个售票窗口卖出100张票的情节,用代码的方式来实现。
一、多线程的创建
多线程的创建有4中方式,在jdk5之前有两种,第1种是继承Thread类的方法,第2种是实现Runnable接口
jdk5之后,也有2种,一种是实现Callable接口,另一种是使用线程池的方式。
核心都是覆盖Thread类中的run(),多个线程执行相同或不同的run()。
class Window implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("售票窗口1:");
t2.setName("售票窗口2:");
t3.setName("售票窗口3:");
t1.start();
t2.start();
t3.start();
}
}
运行结果如下
上面很明显出现了不同窗口卖了同一张票的问题,有些票号没有出现。这是很明显的线程安全问题。
二、同步代码块
class Window implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("售票窗口1:");
t2.setName("售票窗口2:");
t3.setName("售票窗口3:");
t1.start();
t2.start();
t3.start();
}
}
很明显,通过synchronized的使用可以让不同窗口卖出不同的一张票,所有的票也都得到了解决。
三、同步方法
class Window implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
show();
}else {
break;
}
}
}
public synchronized void show(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
ticket--;
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("售票窗口1:");
t2.setName("售票窗口2:");
t3.setName("售票窗口3:");
t1.start();
t2.start();
t3.start();
}
}
也是可以解决上面的问题
帮助文档
快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图
标题复制
四、Lock
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。
class Lock implements Runnable {
private int ticket=100;
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//调用锁定方法lock()
reentrantLock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
ticket--;
}else {
break;
}
}finally {
reentrantLock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Lock t = new Lock();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("售票窗口1:");
t2.setName("售票窗口2:");
t3.setName("售票窗口3:");
t1.start();
t2.start();
t3.start();
}
}
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以
显式加锁、释放锁。
五、总结
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)