以上一节中多窗口卖票出现的问题来说明解决线程安全性问题的三种方法
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
1、操作共享数据(多个线程共同操作的变量)的代码需要被同步;
2、同步监视器,俗称锁。任何一个类的实例化对象都可以充当同步监视器,但是要保证处理共享数据的线程要共用一个同步监视器。因此:
- 如果是Thread类子类实例化方式创建的线程,同步监视器可以用该子类对应的Class类的实例化对象代替,因为一个类在一个程序中只加载一次;
- 而如果是实现Runnable接口方式创建的线程,可以用this作为同步监视器,因为多个线程共用的是一个Runnable接口实现类的实例化对象。
以多窗口卖票程序为例:
例1:
package cn.jingpengchong.thread;
public class Tickets implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true){
synchronized(this) {
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
}else{
break;
}
}
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets tickets = new Tickets();
Thread window1 = new Thread(tickets);
Thread window2 = new Thread(tickets);
Thread window3 = new Thread(tickets);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
运行测试类结果如下:
例2:
package cn.jingpengchong.thread;
public class Tickets extends Thread {
private static int tickets = 100;
@Override
public void run() {
while (true){
synchronized(Tickets.class) {
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
}else{
break;
}
}
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets window1 = new Tickets();
Tickets window2 = new Tickets();
Tickets window3 = new Tickets();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
运行测试类结果如下:
方式二:同步方法
[访问权限] [static] 方法名(参数){
//需要被同步的代码
}
1、操作共享数据(多个线程共同操作的变量)的代码需要被同步;
2、同步监视器,俗称锁。处理共享数据的线程共用一个同步监视器。继承Thread类方式创建的线程中的同步监视器为Thread类的子类对应的Class类的实例化对象,实现Runnable接口方式创建的线程中的同步监视器为this。
以多窗口卖票程序为例:
例:
例1:
package cn.jingpengchong.thread;
public class Tickets implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (true){
synchronized(Tickets.class) {
show();
}
}
}
public synchronized void show(){
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
}else{
Thread.currentThread().stop();
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets tickets = new Tickets();
Thread window1 = new Thread(tickets);
Thread window2 = new Thread(tickets);
Thread window3 = new Thread(tickets);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
运行测试类结果如下:
例2:
package cn.jingpengchong.thread;
public class Tickets extends Thread{
private static int tickets = 100;
@Override
public void run() {
while (true){
synchronized(Tickets.class) {
show();
}
}
}
public static synchronized void show(){
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
}else{
Thread.currentThread().stop();
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets window1 = new Tickets();
Tickets window2 = new Tickets();
Tickets window3 = new Tickets();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
运行测试类结果如下:
注意:
放在synchronized代码块(方法)中的代码需要深思熟虑:
- 里面的代码一定是操作共享数据的代码,并且不能多放,也不能少放;
- synchronized代码块(方法)中的代码是单线程,因此,这种方式虽然安全了,但是执行效率会有所降低。如果将非操作共享数据的代码也放进去,更会使的代码的执行效率大打折扣;
- 如果里面的代码少了判断共享数据的代码、或者修改共享数据的代码等涉及到共享数据的代码,将依然不能解决线程安全问题。
方式三:Lock锁
private [static] ReentrantLock lock = new ReentrantLock();
lock.lock();
//需要被同步的代码
lock.unlock();
说明:
- 该方法为jdk1.5新增功能;
- 该方法与synchronized的区别是,需要手动开启和关闭同步,而synchronized则是代码域执行完毕自动关闭同步;
- 该方法将使得JVM用更少的时间来调度线程,性能更好,建议使用。
以多窗口卖票程序为例:
例1:
package cn.jingpengchong.thread;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets implements Runnable{
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
lock.unlock();
}else{
lock.unlock();
break;
}
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets tickets = new Tickets();
Thread window1 = new Thread(tickets);
Thread window2 = new Thread(tickets);
Thread window3 = new Thread(tickets);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
测试类运行结果如下:
例2:
package cn.jingpengchong.thread;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets extends Thread{
private static int tickets = 100;
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (tickets>0) {
System.out.println(Thread.currentThread().getName() + ":卖票-->" + tickets);
tickets--;
lock.unlock();
}else{
lock.unlock();
break;
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets window1 = new Tickets();
Tickets window2 = new Tickets();
Tickets window3 = new Tickets();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
测试类运行结果如下: