1、多线程共享数据
- 多线程共享数据时,会发生线程不安全的情况
- 多线程共享数据,必须使用同步
2、线程同步
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一时间段只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。
当没有线程同步时:
package com.lemon;
public class ThreadDemo4 {
public static void main(String[] args) {
MyRunnable5 mr = new MyRunnable5();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
/**
* 售票线程:
* 每用一次 票数减一
*/
class MyRunnable5 implements Runnable{
private int ticket = 10; //票数10
private Object obj = new Object(); //要同步的对象
@Override
public void run() {
for(int i = 0; i < 300 ; i++) {
if(ticket > 0 ){
ticket --;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}
}
}
线程同步,有三种方法:
- 同步代码块
- 同步方法
- Lock
(1)同步代码块
synchronized (要同步的对象){ //什么对象都行,但必须是同一个对象 开同一个锁
要同步的操作
}
代码示例:
synchronized的大括号,包住需要同步的代码,我们这里是售票系统
package com.lemon;
public class ThreadDemo4 {
public static void main(String[] args) {
MyRunnable5 mr = new MyRunnable5();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
/**
* 售票线程:
* 每用一次 票数减一
*/
class MyRunnable5 implements Runnable{
private int ticket = 10; //票数10
private Object obj = new Object(); //要同步的对象 锁
@Override
public void run() {
for(int i = 0; i < 300 ; i++) {
synchronized (obj){
if(ticket > 0 ){
ticket --;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}
}
}
}
一般用this当前对象代表同步锁
class MyRunnable5 implements Runnable{
private int ticket = 10; //票数10
// private Object obj = new Object(); //同步锁
@Override
public void run() {
for(int i = 0; i < 300 ; i++) {
synchronized (this){
if(ticket > 0 ){
ticket --;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}
}
}
}
(2)同步方法
就是把上面需要同步的代码,封装在方法中,给方法加锁,然后在需要的地方调用这个同步方法
同步的对象是当前对象(this)
代码示例:
package com.lemon;
public class ThreadDemo4 {
public static void main(String[] args) {
MyRunnable5 mr = new MyRunnable5();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
/**
* 售票线程:
* 每用一次 票数减一
*/
class MyRunnable5 implements Runnable {
private int ticket = 10; //票数10
// private Object obj = new Object(); //同步锁
@Override
public void run() {
for (int i = 0; i < 300; i++) {
method();
// synchronized (this) {
// if (ticket > 0) {
// ticket--;
// try {
// Thread.sleep(1000); //休眠1秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("票数还剩余:" + ticket + "张");
// }
// }
}
}
//同步方法:同步的对象是当前对象(this)
public synchronized void method(){
if (ticket > 0) {
ticket--;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}
}
(3)Lock(ReentranLock)
Lock
实现提供比使用synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
。
代码示例:
一般 解锁语句 放在finally语句块中,防止死锁
package com.lemon;
import java.util.concurrent.locks.ReentrantLock;
/**
* 1、多线程共享数据时,会发生线程不安全的情况
* 2、多线程共享数据,必须使用同步
* 3、实现线程同步的方法:
* (1)使用同步代码块
* (2)使用同步方法
* (3)使用Lock(更加灵活的代码控制)
*
* @author lemonsun
*/
public class ThreadDemo4 {
public static void main(String[] args) {
MyRunnable5 mr = new MyRunnable5();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
/**
* 售票线程:
* 每用一次 票数减一
*/
class MyRunnable5 implements Runnable {
private int ticket = 10; //票数10
// private Object obj = new Object(); //同步锁
@Override
public void run() {
for (int i = 0; i < 300; i++) {
//method(); //同步方法
method1(); //Lock互斥锁
// synchronized (this) { //同步代码块
// if (ticket > 0) {
// ticket--;
// try {
// Thread.sleep(1000); //休眠1秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("票数还剩余:" + ticket + "张");
// }
// }
}
}
/**
* 同步方法:
*/
//同步方法:同步的对象是当前对象(this)
public synchronized void method(){
if (ticket > 0) {
ticket--;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}
/**
* 互斥锁:
* 更加灵活
*/
//Lock实现同步
ReentrantLock lock = new ReentrantLock();
public void method1(){
lock.lock(); //锁
try {
if (ticket > 0) {
ticket--;
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("票数还剩余:" + ticket + "张");
}
}finally {
lock.unlock(); //释放锁
}
}
}
同步准则:
多线程共享数据时,会有安全问题,使用同步可以解决安全问题,但同时会牺牲性能
当使用synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险方面大有帮助
(1)使代码块保持简短,把不随线程变化的预处理和后处理移出synchronized块
(2)不要阻塞,如
(3)在持有锁的时候,不要对其他对象调用同步方法