一.引出线程安全问题
多个线程操作共享数据时,如果线程不完整,则很可能出现线程安全问题,线程执行不确定导致结果不确定.
这里有一个三窗口买票程序,共100张票
public class Main2 {
public static void main(String[] args) {
myThread2 m = new myThread2();
//设置窗口
Thread t1 = new Thread(m);
t1.setName("窗口1");
Thread t2 = new Thread(m);
t2.setName("窗口2");
Thread t3 = new Thread(m);
t3.setName("窗口3");
//开始卖票
t1.start();
t2.start();
t3.start();
}
}
class myThread2 implements Runnable{
//多窗口买票
private int ticket = 100;
@Override
public void run() {
while(ticket>=0){//若有余票则继续
try {
//线程睡眠,但不释放锁,其他线程无法执行
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+ticket);//卖票
ticket--;
}
}
}
sleep()的目的是加上后线程会阻塞,操作未完成,其他线程参与进来的概率增加
以上运行结果会出现错票问题,如余票为-2的情况
解决方式一:同步代码块(锁)
1.Runnable解决方法
使用同步代码块将共享数据(ticket)所在的地方包起来
线程操作未完成时,锁不打开,其他线程无法参与进来
使用关键字
synchronized (同步监视器){....}
任何一个对象都可以充当任务监视器
一般在run()方法外定义对象(不能直接创建)
传到同步代码块中
多线程必须使用同一把锁(Object对象)
class myThread2 implements Runnable{
//多窗口买票
private int ticket = 100;//只能
Object object = new Object();
@Override
public void run() {
synchronized (object) {//同步代码块
while (ticket >= 0) {//若有余票则继续
try {
//线程睡眠,但不释放锁,其他线程无法执行
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread().getName() + "卖票,剩余票数:" + ticket);//卖票
ticket--;
}
}
}
}
2.继承Thread类解决方法
如果用刚才的同步代码块解决继承Thread类方式卖票的方式肯定是不行的
因为此方式多窗口卖票要创造三个对象,不能共用同一把锁
那么解决方式就是将Object类的对象设置为静态,这样就共享同一个对象了
public class Main {
public static void main(String[] args) {
MyThread m1 = new MyThread();
m1.setName("一号窗口");
MyThread m2 = new MyThread();
m2.setName("二号窗口");
MyThread m3 = new MyThread();
m3.setName("三号窗口");
m1.start();
m2.start();
m3.start();
}
}
class MyThread extends Thread{
private static int ticket = 100;
private static Object object = new Object();
@Override
public void run() {
synchronized (object) {
while (ticket>=0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread().getName() + "卖票,剩余票数:" + ticket);//卖票
ticket--;
}
}
}
}
还有一种方式就是考虑使用当前类作为同步监视器
synchronized (MyThread.class) {}
类也是一种对象
解决方式二:同步方法
同步代码块看着有点别扭,如果操作都在一个方法中那就可以直接声明该方法了
class myThread2 implements Runnable{
//多窗口买票
private int ticket = 100;//只能
Object object = new Object();
@Override
public void run() {
synchronized (object) {//同步代码块
while (ticket >= 0) {//若有余票则继续
show();
}
}
}
private synchronized void show(){//声明同步方法
if(ticket>=0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread().getName() + "卖票,剩余票数:" + ticket);//卖票
ticket--;
}
}
}
这个时候同步监视器就是this了
这是实现Runnable类的解决方式,
那么继承Thread的解决方式则是在该show方法前面加上static就行