1、关于线程安全
通过同步机制,synchronized 关键字,可以解决了线程的安全问题,操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低,这也是同步机制的局限性。
- Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性;
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象;
- 关键字 synchronized 与对象的互斥锁的联系:当某个对象用 synchronized 修饰时,表明该对象在任意时刻只能由一个线程访问;
- 同步的局限性:由于同步机制将多线程变成了一个串行化的过程,导致程序的执行效率降低;
- 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是同一个对象);
- 同步方法(静态的)的锁为当前类本身;
补充一下:
- 同步方法如果没有用 static 修饰,默认锁对象为 this;
- 如果方法使用 static 修饰,默认锁对象为:当前类.class;
2、如何解决线程安全问题?
2.1、同步代码块
使用 synchronized 修饰代码块
synchronized(同步监视器){//需要被同步的代码}
说明
1、操作共享数据的代码,即为需要被同步的代码
2、共享数据:多个线程共同操作的变量。比如ticket就是共享数据
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。(多个线程必须要公用同一把锁)
2.2、同步方法
使用 synchronized 修饰方法
public synchronized void f1() {//同步监视器:this
// 需要被同步的代码
}
另外,如果操作共享数据的代码完整的声明在同一个方法中,我们不妨将此方法声明为同步的。
2.3:Lock锁方式
3、例子
以卖票为例,三个窗口共同售卖100张门票。
3.1、非线程安全的实现方法
问题
会出现重票、错票,出现了线程安全问题。
原因
当某个线程操作车票过程中,尚未完成操作时,其他线程参与进来,也操作车票。
解决
当一个线程A在操作ticket时,其他线程不能参与进来。直到线程A操作完ticket完成时,其他线程才可以开始操作ticket。这种情况即使线程A出现了阻塞,也不能被改变。
public class Main {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
private int ticket = 100;
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
3.2、线程安全的实现方法
0x1 同步代码块
public class Main {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
private int ticket = 100;
Object obj = new Object();
public void run() {
while (true) {
//可以考虑用this
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
0x2 同步方法
实现runnable接口
public class Main {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable {
private int ticket = 100;
public void run() {
while (true) {
sell();
}
}
public synchronized void sell() {//同步监视器:this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
}
}
}
继承Thread类
public class Main {
public static void main(String[] args) {
Thread t1 = new Windows();
Thread t2 = new Windows();
Thread t3 = new Windows();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Windows extends Thread {
private static int ticket = 100;
public void run() {
while (true) {
sell();
}
}
public static synchronized void sell() {//同步监视器:this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
}
}
}
