多线程同步
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程
没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。
线程安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。
不出现数据不一致或者数据污染。
线程不安全
就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
1、下面例子是线程不安全的例子
public class Example11 {
public static void main(String[] args) {
SaleThread saleThread = new SaleThread(); // 创建Ticket1 对象
// 创建并开启四个线程
new Thread(saleThread, "线程一").start();
new Thread(saleThread, "线程二").start();
new Thread(saleThread, "线程三").start();
new Thread(saleThread, "线程四").start();
}
}
// 定义Ticket1类实现Runnable接口
class SaleThread implements Runnable {
private int tickets = 10; // 10张票
public void run() {
while (tickets > 0) {
try {
Thread.sleep(10); // 经过此处的线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---卖出的票"
+ tickets--);
}
}
}
结果:
线程三---卖出的票10
线程二---卖出的票9
线程四---卖出的票9
线程一---卖出的票8
线程二---卖出的票7
线程三---卖出的票6
线程一---卖出的票5
线程四---卖出的票5
线程三---卖出的票4
线程二---卖出的票3
线程一---卖出的票2
线程四---卖出的票2
线程二---卖出的票0
线程三---卖出的票1
线程四---卖出的票-1
线程一---卖出的票-1
由结果看出不安全线程造成的结果是同一资源可能被多次使用,与现实不符。造成上面的原因:①如9被读2次,②输出的数有负数。
①假设线程一此时出售9号票,在售票之前通过sleep()方法让线程休眠,因为调用sleep()函数不会释放资源,所以当线程一睡眠时,9号票还在它手里,这时线程三开始买票,
因为9号票还没卖出去,线程三就卖了9号票,当线程一醒了之后,又把手里的9号票卖了出去,所以就卖了2次。
②假设线程一此时出售1号票,对票号进行判断后,进入while循环,在售票之前通过sleep()方法让线程休眠,这时线程二进行售票,由于此时票号仍未1,因此线程二也会进入
循环,同理,四个线程都会进入while循环,休眠结束后,四个线程都会进行售票,这样就相当于将票减了四次。
二、实现同步
为了实现线程安全,即资源不被重复使用,Java提供了同步机制:①同步代码块,②同步方法(都用synchronized关键字来修饰)
①同步代码块
synchronized (lock) {其中lock是一个锁对象,它是同步代码的关键。当线程执行同步代码块时,首先会检查锁对象的标志位。
// 同步代码块
}
lock默认情况下标志位为1,线程执行同步代码块时,lock变为0,别的线程就不能执行这段代码块了,当这个线程完事之后,lock变回1,别的线程可以调用这个代码了。将上面代码用synchronized后如下:
class Ticket1 implements Runnable {
private int tickets = 10; // 定义变量tickets,并赋值10
Object lock = new Object(); // 定义任意一个对象,用作同步代码块的锁
public void run() {
while (true) {
synchronized (lock) { // 定义同步代码块
try {
Thread.sleep(10); // 经过的线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ "---卖出的票" + tickets--);
} else { // 如果 tickets小于0,跳出循环
break;
}
}
}
}
}
public class Example2 {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1(); // 创建Ticket1对象
// 创建并开启四个线程
new Thread(ticket, "线程一").start();
new Thread(ticket, "线程二").start();
new Thread(ticket, "线程三").start();
new Thread(ticket, "线程四").start();
}
}
结果:
线程一---卖出的票10
线程一---卖出的票9
线程一---卖出的票8
线程一---卖出的票7
线程一---卖出的票6
线程一---卖出的票5
线程一---卖出的票4
线程一---卖出的票3
线程一---卖出的票2
线程一---卖出的票1
由结果知道实现了线程安全,资源不被多次使用。
②同步方法
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程会发生阻塞,直到当前线程访问完毕后,其他线程才有机会访问。如下例
class Ticket1 implements Runnable {结果:
private int tickets = 10;
public void run() {
while (true) {
saleTicket(); // 调用售票方法
if (tickets <= 0) {
break;
}
}
}
// 定义一个同步方法saleTicket()
private synchronized void saleTicket() {
if (tickets > 0) {
try {
Thread.sleep(10); // 经过的线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---卖出的票"
+ tickets--);
}
}
}
public class Example {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1(); // 创建Ticket1对象
// 创建并开启四个线程
new Thread(ticket,"线程一").start();
new Thread(ticket,"线程二").start();
new Thread(ticket,"线程三").start();
new Thread(ticket,"线程四").start();
}
}
线程一---卖出的票10
线程一---卖出的票9
线程一---卖出的票8
线程一---卖出的票7
线程一---卖出的票6
线程一---卖出的票5
线程一---卖出的票4
线程一---卖出的票3
线程一---卖出的票2
线程一---卖出的票1