卖票的过程中出现了线程安全问题,有重票和错票的问题。 为什么会出现这个问题? 当某个线程在操作卖票的过程中尚未完成卖票操作,其他线程也参与进来操作卖票,就会出现线程安全问题,(主要是共享数据(nums)的问题) 怎么去解决这个问题? 当某个线程在操作卖票的时候,其他线程不能参与进来,直到这个线程操作完成(即使这个线程阻塞了也得等这个线程执行完成),其他线程才能进来。 java里面我们通过同步机制来解决线程安全问题 方式一:同步代码块 synchronized(同步监视器){ } 说明: (1)操作共享数据的代码即为需要被同步的代码,不能包含的代码太多会浪费资源,降低效率。 (2)共享数据:多个线程共同操作的变量。比如卖票里面的nums (3)同步监视器俗称锁,任何一个类的对象都可以充当锁,但是前提是这个对象对于多个线程来说都是唯一的。 补充:在实现Runnable接口创建线程对象的方式中,我们可以考虑使用this充当同步监视器,如果是使用继承Thread类的方式 新建的线程对象则慎用This(可能会有多个对象),可以使用类.class来当同步监视器。
package com.jinjian;
/**
卖票的过程中出现了线程安全问题,有重票和错票的问题。
为什么会出现这个问题?
当某个线程在操作卖票的过程中尚未完成卖票操作,其他线程也参与进来操作卖票,就会出现线程安全问题,(主要是共享数据(nums)的问题)
怎么去解决这个问题?
当某个线程在操作卖票的时候,其他线程不能参与进来,直到这个线程操作完成(即使这个线程阻塞了也得等这个线程执行完成),其他线程才能进来。
java里面我们通过同步机制来解决线程安全问题
方式一:同步代码块
synchronized(同步监视器){
}
说明:
(1)操作共享数据的代码即为需要被同步的代码,不能包含的代码太多会浪费资源,降低效率。
(2)共享数据:多个线程共同操作的变量。比如卖票里面的nums
(3)同步监视器俗称锁,任何一个类的对象都可以充当锁,但是前提是这个对象对于多个线程来说都是唯一的。
补充:在实现Runnable接口创建线程对象的方式中,我们可以考虑使用this充当同步监视器,如果是使用继承Thread类的方式
新建的线程对象则慎用This(可能会有多个对象),可以使用类.class来当同步监视器。
方式二:同步方法
*/
class Window implements Runnable{
private int nums = 100;
Object object = new Object();
@Override
public void run() {
while (true) {
// synchronized (object) {
synchronized (this) {
if (nums > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为: " + nums);
nums--;
} else {
break;
}
}
}
}
}
class Window1 extends Thread{
private static int nums = 100;
@Override
public void run() {
while (true) {
synchronized (Window1.class) {
if (nums > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为: " + nums);
nums--;
} else {
break;
}
}
}
}
}
public class ThreadTest03 {
public static void main(String[] args) {
//这样相当于新建了三个Window1对象,要想共享nums变量就需要用static修饰nums。
//而且不能用this作为对象监视器,三个对象有三个this不能保证对象监视器的唯一
//可以使用Window1.class来作为对象监视器
Window1 w1 = new Window1();
Window1 w2 = new Window1();
Window1 w3 = new Window1();
w1.start();
w2.start();
w3.start();
//使用实现Runnable接口来新建线程对象
// Window window = new Window();
//这样nums就是共用的变量,不需要加static,因为只创建了一个Window对象,下面三个线程对象共用一个Window对象
// Thread t1 = new Thread(window);
// Thread t2 = new Thread(window);
// Thread t3 = new Thread(window);
//
// t1.start();
// t2.start();
// t3.start();
}
}
使用同步方法来解决线程安全问题, (1)同步方法依然涉及到同步监视器,只是不需要我们说明 (2)非静态的同步方法的监视器默认是this,静态方法的同步监视器是当前类本身
package com.jinjian; /** 使用同步方法来解决线程安全问题, (1)同步方法依然涉及到同步监视器,只是不需要我们说明 (2)非静态的同步方法的监视器默认是this,静态方法的同步监视器是当前类本身 */ class Window2 implements Runnable{ private int nums = 100; @Override public void run() { while (true) { seal(); } } private synchronized void seal(){//这样默认的同步监视器是this if (nums > 0) { System.out.println(Thread.currentThread().getName() + "卖票,票号为: " + nums); nums--; } } } class Window3 extends Thread{ private static int nums = 100; @Override public void run() { while (true) { // synchronized (Window1.class) { seal(); //} } } // private synchronized void seal(){//这样是不行的,This不唯一有三个对象 private static synchronized void seal(){//这样默认的同步监视器是Window3.class if (nums > 0) { System.out.println(Thread.currentThread().getName() + "卖票,票号为: " + nums); nums--; } } } public class ThreadTest04 { public static void main(String[] args) { // Window2 window2 = new Window2(); // // Thread t1 = new Thread(window2); // Thread t2 = new Thread(window2); // Thread t3 = new Thread(window2); // // t1.start(); // t2.start(); // t3.start(); Window3 w1 = new Window3(); Window3 w2 = new Window3(); Window3 w3 = new Window3(); w1.start(); w2.start(); w3.start(); } }
死锁的情况:
演示程序死锁的问题 (1)不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的资源,就形成了线程的死锁 (2)出现死锁后不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续。所以要避免出现死锁
(3)a线程等待拿到s2的锁,执行完成后释放s1的锁。b线程等待拿到s1的锁,执行完成后释放s2的锁。这样就形成了死锁
package com.jinjian;
import javax.swing.plaf.synth.SynthOptionPaneUI;
/**
演示程序死锁的问题
(1)不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的资源,就形成了线程的死锁
(2)出现死锁后不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续。所以要避免出现死锁
(3)a线程等待拿到s2的锁,执行完成后释放s1的锁。b线程等待拿到s1的锁,执行完成后释放s2的锁。这样就形成了死锁
*/
public class TestThreadDeadLock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (s1){
s1.append('a');
s2.append('1');
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append('b');
s2.append('2');
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append('C');
s2.append('3');
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append('D');
s2.append('4');
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决线程安全问题方式三:使用Lock-----------JDK5新增 面试题: Lock和synchronized之间的异同? 相同:二者都可以解决线程安全问题 不同:synchronized机制在执行完相应代码后会自动释放同步监视器, Lock需要手动启动同步(lock),和结束同步也需要手动释放锁(unlock) 几张同步方式的优先使用顺序 Lock-----synchronized同步代码块(已经进入了方法体,分配了相应的资源)------synchronized方法(在方法体之外)
package com.jinjian;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
解决线程安全问题方式三:使用Lock-----------JDK5新增
面试题:
Lock和synchronized之间的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应代码后会自动释放同步监视器,
Lock需要手动启动同步(lock),和结束同步也需要手动释放锁(unlock)
几张同步方式的优先使用顺序
Lock-----synchronized同步代码块(已经进入了方法体,分配了相应的资源)------synchronized方法(在方法体之外)
*/
class Window5 implements Runnable{
private int nums = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//上锁,在锁之间的代码块会被锁住,和synchronized包住的代码块一样,实现线程安全
lock.lock();
if (nums > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为: " + nums);
nums--;
} else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
public class TestReentrantLock {
public static void main(String[] args) {
Window5 window5 = new Window5();
Thread t1 = new Thread(window5);
Thread t2 = new Thread(window5);
Thread t3 = new Thread(window5);
t1.start();
t2.start();
t3.start();
}
}