线程同步
线程安全的问题
问题的提出
多个线程执行的不确定性引起结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据
线程安全举例
多窗口卖票问题
public class WindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
Thread thread3 = new Thread(window);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0 ){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
出现重票和错票
原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作车票。
如何解决:当线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以继续开始操作ticket。这种情况即使a出现阻塞,也不能被改变。在Java中,我们通过同步机制解决线程安全问题。
同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
操作共享数据的代码,即为需要被同步的代码。
共享数据:多个线程共同操作的变量
同步监视器:俗称:锁。任何一个类的对象都可以充当锁。
要求:多个线程必须共用同一把锁
同步代码块处理实现Runnable的线程安全问题
对象object作为锁,或者使用this关键字表示当前对象作为锁
class Window implements Runnable{
private int ticket = 1000;
Object object = new Object();
@Override
public void run() {
while (true){
synchronized (this){//synchronized (object){
if (ticket > 0 ){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
同步代码处理继承Thread类的线程安全问题
因为创建了三个Window2()对象,所以要将ticket和object用static修饰保证三个线程共用一个锁和ticket。
更简单的可以使用类锁 Window2.class
,保证三个线程共用一个锁
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 extends Thread {
private static int ticket = 100;
private static Object object = new Object();
@Override
public void run() {
while (true) {
synchronized(Window2.class){//synchronized (object) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明成同步的
实现Runnable
public class WindowTest3 {
public static void main(String[] args) {
Window3 window3 = new Window3();
Thread thread1 = new Thread(window3);
Thread thread2 = new Thread(window3);
Thread thread3 = new Thread(window3);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
do {
show();
} while (ticket != 0);
}
private synchronized void show(){//同步监视器this
if (ticket > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
继承Thread
把同步方法声明成静态的,静态方法随着类的加载而加载
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
do {
show();
} while (ticket != 0);
}
private static synchronized void show(){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
类锁和对象锁的区别
类锁是一个class其中的静态方法和静态变量在内存中只会加载和初始化一份,所以,一旦一个静态的方法被申明为synchronized,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。
类锁是加载类上的,而类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。
类锁属于一个类,类似静态变量;多个实例对象对应同一个锁。
对象锁属于对象实例,类似类属性变量;每一个实例对象,对应一个锁。