目录
目录
方法一:同步代码块的第二个例子。把锁和ticket都改为静态
错误示范:show方法的锁是this,this指的是Window4的对象,即t1,t2,t3,3个线程用的不是同一个锁,所以线程不安全
正确示范:把show方法改为静态的,private static synchronized void show(),此时同步监视器(锁)是Window4.class
举例:三个窗口卖票
class Window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true) {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w=new Window1();
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();
}
}
运行结果:出现重票
问题:卖票的过程中,出现了重票、错票---->出现了线程安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
窗口2:卖票,票号为:100
窗口1:卖票,票号为:100
窗口3:卖票,票号为:100
窗口1:卖票,票号为:97
窗口2:卖票,票号为:97
窗口3:卖票,票号为:97
.......
窗口3:卖票,票号为:7
窗口3:卖票,票号为:4
窗口1:卖票,票号为:4
窗口2:卖票,票号为:4
窗口3:卖票,票号为:1
窗口1:卖票,票号为:1
窗口2:卖票,票号为:0
如何解决?
当线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况即使是线程a出现了阻塞,也不能被改变
在Java中,程序使用同步机制来解决
方式1:同步代码块
synchronized (同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
4.多个线程公用同一个锁。
方式2:同步方法
如果操作共享数据的代码完整声明在一个方法中,我们不妨将此方法声明为同步方法。
方式一:同步代码块,第一个例子
同步代码块解决Runnable接口创建的线程的安全问题
class Window1 implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true) {
synchronized(obj) {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w=new Window1();
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();
}
}
运行结果:
没有重票问题。但是,全是窗口3在卖票。没有3个窗口交替出现。理论上讲,没问题。
窗口3:卖票,票号为:100
窗口3:卖票,票号为:99
窗口3:卖票,票号为:98
窗口3:卖票,票号为:97
窗口3:卖票,票号为:96
.......
改进:
把sleep放到synchronized()上面。
class Window1 implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w=new Window1();
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();
}
}
运行结果:
没有重票问题,3个窗口交替卖票
窗口1:卖票,票号为:100
窗口2:卖票,票号为:99
窗口3:卖票,票号为:98
窗口1:卖票,票号为:97
窗口2:卖票,票号为:96
窗口3:卖票,票号为:95
......
窗口2:卖票,票号为:9
窗口1:卖票,票号为:8
窗口3:卖票,票号为:7
窗口1:卖票,票号为:6
窗口2:卖票,票号为:5
窗口2:卖票,票号为:4
窗口1:卖票,票号为:3
窗口3:卖票,票号为:2
窗口1:卖票,票号为:1
方法一:同步代码块的第二个例子。把锁和ticket都改为静态
因为Window2是线程类,3个线程要共用同一种ticket和锁
同步代码块处理继承Thread类的线程安全问题
锁可以用synchronized(Window2.class),Window2.class只会加载一次,类也是对象
class Window2 extends Thread{
private static int ticket=100;
// 静态锁,确保所有线程公用一个锁
private static Object obj=new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName()+"卖票,票号为:"+ticket);
ticket--;
}
else {
break;
}
}
}
}
}
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();
}
}
运行结果:
窗口2卖票,票号为:100
窗口2卖票,票号为:99
窗口2卖票,票号为:98
窗口2卖票,票号为:97
窗口2卖票,票号为:96
....................
窗口3卖票,票号为:6
窗口3卖票,票号为:5
窗口3卖票,票号为:4
窗口3卖票,票号为:3
窗口3卖票,票号为:2
窗口3卖票,票号为:1
方法二:同步方法,第一个例子
同步方法解决Runnable接口创建的线程的安全问题
class Window3 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true) {
show();
if(ticket<=0) break;
}
}
//同步方法同步监视器是this
// 在本例子中,this指的是当前类的对象,即wtc
private synchronized void show() {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" ticket:"+ticket);
--ticket;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 wtc=new Window3();
Thread th1=new Thread(wtc);
Thread th2=new Thread(wtc);
Thread th3=new Thread(wtc);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
方式二:同步方法第二个例子
解决继承Thread类创建的线程的线程安全问题
错误示范:show方法的锁是this,this指的是Window4的对象,即t1,t2,t3,3个线程用的不是同一个锁,所以线程不安全
class Window4 extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true) {
show();
if(ticket<=0) break;
}
}
/**
* 线程不安全
* private synchronized void show()表示同步监视器(锁)是this
* 这里的this指的是3个Window4的对象,即t1,t2,t3
*/
private synchronized void show() {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName()+"卖票,票号为:"+ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
// window4是线程类
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();
}
}
正确示范:把show方法改为静态的,private static synchronized void show(),此时同步监视器(锁)是Window4.class
class Window4 extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true) {
show();
if(ticket<=0) break;
}
}
/**
* 线程安全
* private staic synchronized void show()表示同步监视器(锁)是Window4.class
* Window4.class是唯一的
*/
private static synchronized void show() {
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
// window4是线程类
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();
}
}
同步方法总结:
1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
2.非静态的同步方法,同步监视器是this
静态方法的同步方法,同步监视器是:当前类本身,即xxx.class
方式三:Lock锁方式解决线程安全问题
JDK5.0新增
class Window5 implements Runnable{
private int ticket=100;
//true是公平锁,个进程排队获得锁,释放锁。
private ReentrantLock lock=new ReentrantLock(true);
@Override
public void run() {
while (true) {
// 调用锁定方法
lock.lock();
try {
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
--ticket;
}else {
break;
}
} finally {
// 调用解锁方法
lock.unlock();
}
}
}
}
public class WindowTest5 {
public static void main(String[] args) {
Window5 win=new Window5();
// 3个线程公用1个win对象
Thread th1=new Thread(win);
Thread th2=new Thread(win);
Thread th3=new Thread(win);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
运行结果:
窗口1:100
窗口2:99
窗口3:98
窗口1:97
窗口2:96
窗口3:95
窗口1:94
................
窗口1:7
窗口2:6
窗口3:5
窗口1:4
窗口2:3
窗口3:2
窗口1:1
synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同 :synchronized机制在执行完相应的同步代码后,自动的释放同步监视器。Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock() )