当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。
public class SaleTicketDemo1 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
public void run(){
int total = 100;
while(total>0) {
System.out.println(getName() + "卖出一张票,剩余:" + --total);
}
}
}
public class SaleTicketDemo2 {
public static void main(String[] args) {
TicketSaleThread t1 = new TicketSaleThread();
TicketSaleThread t2 = new TicketSaleThread();
TicketSaleThread t3 = new TicketSaleThread();
t1.start();
t2.start();
t3.start();
}
}
class TicketSaleThread extends Thread{
private int total = 10;
public void run(){
while(total>0) {
System.out.println(getName() + "卖出一张票,剩余:" + --total);
}
}
}
public class SaleTicketDemo3 {
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
TicketThread t2 = new TicketThread();
TicketThread t3 = new TicketThread();
t1.start();
t2.start();
t3.start();
}
}
class TicketThread extends Thread{
private static int total = 10;
public void run(){
while(total>0) {
try {
Thread.sleep(10);//加入这个,使得问题暴露的更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "卖出一张票,剩余:" + --total);
}
}
}
共享,但是有线程安全问题
public class SaleTicketDemo3 {
public static void main(String[] args) {
TicketSaleRunnable tr = new TicketSaleRunnable();
Thread t1 = new Thread(tr,"窗口一");
Thread t2 = new Thread(tr,"窗口一");
Thread t3 = new Thread(tr,"窗口一");
t1.start();
t2.start();
t3.start();
}
}
class TicketSaleRunnable implements Runnable{
private int total = 10;
public void run(){
while(total>0) {
try {
Thread.sleep(10);//加入这个,使得问题暴露的更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
}
}
}
但是存在0票和-1票情况
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。 格式:
同步锁对象:
锁对象可以是任意类型。
多个线程对象 要使用同一把锁。
1 同步方法的锁对象问题
(1)静态方法:当前类的Class对象
(2)非静态方法:this
public class SaleTicketSafeDemo1 {
public static void main(String[] args) {
// 2、创建资源对象
Ticket ticket = new Ticket();
// 3、启动多个线程操作资源类的对象
Thread t1 = new Thread("窗口一") {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
};
Thread t2 = new Thread("窗口二") {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
};
Thread t3 = new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
// 1、编写资源类
class Ticket {
private int total = 10;
//非静态方法隐含的锁对象就是this
public synchronized void sale() {
if (total > 0) {
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
} else {
throw new RuntimeException(Thread.currentThread().getName() + "发现没有票了");
}
}
public int getTotal() {
return total;
}
}
同步锁对象:
锁对象可以是任意类型。
多个线程对象 要使用同一把锁。
习惯上先考虑this,但是要注意是否同一个this
public class SaleTicketSafeDemo1 {
public static void main(String[] args) {
// 2、创建资源对象
Ticket ticket = new Ticket();
// 3、启动多个线程操作资源类的对象
Thread t1 = new Thread("窗口一") {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
};
Thread t2 = new Thread("窗口二") {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
};
Thread t3 = new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(10);// 加入这个,使得问题暴露的更明显
ticket.sale();
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
}, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
// 1、编写资源类
class Ticket {
private int total = 10;
public void sale() {
synchronized (this) {
if (total > 0) {
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total);
} else {
throw new RuntimeException(Thread.currentThread().getName() + "发现没有票了");
}
}
}
public int getTotal() {
return total;
}
}
锁的范围太小:不能解决安全问题
锁的范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。
锁范围太小示例
public class SaleTicketSafeDemo2 {
public static void main(String[] args) {
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr,"窗口一");
Thread t2 = new Thread(tr,"窗口二");
Thread t3 = new Thread(tr,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
class TicketRunnable implements Runnable {
private int ticket = 10;
@Override
public void run() {
while(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
//if (ticket > 0) {//条件没有锁进去
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
//}
}
}
}
}
锁范围太大示例
public class SaleTicketSafeDemo3 {
public static void main(String[] args) {
TicketRunnableDemo tr = new TicketRunnableDemo();
Thread t1 = new Thread(tr,"窗口一");
Thread t2 = new Thread(tr,"窗口二");
Thread t3 = new Thread(tr,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
class TicketRunnableDemo implements Runnable {
private int ticket = 10;
@Override
public synchronized void run() {
while(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
}
}
}