文章目录
1、例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
4.通过同步机制来解决线程安全的问题
2、Java解决方案:同步机制(Run)
2.1、同步代码块
(synchronized
)
synchronized(同步监视器){
// 需要被同步的代码
}
说明:
①:操作共享数据的代码,即为共享代码;
②:共享数据:多个线程共同操作的变量。
③:同步监视器:俗称锁,任何一个类的对象都可以来充当锁。
④:要求:多个线程必须要共用同一把锁。
2.2、实例代码
package Thread;
/**
* @author Muluo
* @create 2021-01-18 21:21
*/
class window implements Runnable{
private int ticket = 100;
// 同步监视器
Object obj = new Object();
@Override
public void run() {
while(true) {
// 同步代码块
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class WindowTicket {
public static void main(String[] args) {
window window = new window();
Thread thread1 = new Thread(window);
thread1.setName("窗口一");
thread1.start();
Thread thread2 = new Thread(window);
thread2.setName("窗口二");
thread2.start();
Thread thread3 = new Thread(window);
thread3.setName("窗口三");
thread3.start();
}
}
3、例子:创建个窗口卖票,总票数为100张.使用继承Thread的方式
注意:此方式要注意监视器中的对象 声明为一个静态的,从而确保是一个对象
package Thread;
/**
* @author Muluo
* @create 2021-01-18 21:52
*/
class window2 extends Thread{
private static int ticket = 100;
// 同步监视器 要声明为静态的,才能保证是一个对象,否则依然会出现不安全的问题
private static Object obj = new Object();
@Override
public void run() {
while(true) {
// 同步代码块 this(当前对象也可以) window2.class -> 反射 类也是对象,所以也可以作为同步监视器 是唯一的,只会加载一次
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class WindowTicket2 {
public static void main(String[] args) {
window2 window2 = new window2();
window2 window3 = new window2();
window2 window4 = new window2();
window2.start();
window3.start();
window4.start();
}
}
补充:
1、在实现Runnable接口创建多线程的方式中,我们可以
考虑使用this
充当同步监视器。
2、在继承Thread类创建多线程的方式中,慎用this
充当同步监视器,考虑使用当前类充当同步监视器。
(反射)
2.3、同步方法
实现Runnable接口的同步方法
package Thread;
/**
* 使用同步方法解决实现Runable 接口的线程问题
*
* 同步仍然涉及到同步监视器,只是不需要显示的声明
* 非静态的同步方法,同步监视器是this
* 静态的同步方法,同步监视器是当前类本身
* @author Muluo
* @create 2021-01-19 19:43
*/
class window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() { // 同步监视器就是this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class WindowTicket3 {
public static void main(String[] args) {
window window = new window();
Thread thread1 = new Thread(window);
thread1.setName("窗口一");
thread1.start();
Thread thread2 = new Thread(window);
thread2.setName("窗口二");
thread2.start();
Thread thread3 = new Thread(window);
thread3.setName("窗口三");
thread3.start();
}
}
同步方法 继承Thread类来实现线程安全问题
package Thread;
/**
* 同步方法 继承Thread类来实现线程安全问题
@author Muluo
* @create 2021-01-19 19:53
*/
class window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
// 变成static synchronized
private static synchronized void show() { // 同步监视器是Window4
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class WindowTicket4 {
public static void main(String[] args) {
window2 window2 = new window2();
window2 window3 = new window2();
window2 window4 = new window2();
window2.start();
window3.start();
window4.start();
}
}
同步仍然涉及到同步监视器,只是不需要显
非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身
2.4利用同步锁解决线程问题(Lock___JDK 5.0以后新增)
package Thread;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程的安全问题,方式三,Lock方式,JDK5.0新增
* @author Muluo
* @create 2021-01-19 20:12
*/
class WindowLock implements Runnable{
private int ticket = 100;
// ctrl + p
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 调用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 {
// 解锁
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
WindowLock w1 = new WindowLock();
Thread thread1 = new Thread(w1);
thread1.start();
Thread thread2 = new Thread(w1);
thread2.start();
Thread thread3 = new Thread(w1);
thread3.start();
}
}
!!!synchronized与Lock的异同
同:二者都可以解决线程安全问题
异:
1.synchronized执行完相应的代码后,会自动释放同步监视器
2.Lock需要手动的启动同步(Lock()),同时结束同步也需要手动去实现(unlock())
优先使用Lock更灵活