线程同步(多个线程操作同一个资源)
- 线程同步: 其实就是一种等待机制,多个对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
- 线程同步概念保证了某个资源在某一时刻只能有一个线程来访问,来保证共享数据的一致性。
不安全的线程使用案例
//线程不安全,买到负数的票,或者买到了同一张票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket=new BuyTicket();
Thread thread=new Thread(buyTicket,"小张");
Thread thread2=new Thread(buyTicket,"小红");
Thread thread3=new Thread(buyTicket,"小李");
thread.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;
boolean flag=true;//线程停止方式使用外部停止方式
@Override
public void run() {
while (flag){
ButTicket();
}
}
public void ButTicket(){
if(ticketNums<=0){
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNums--+"张票");
}
}
代码结果:
线程安全解决
同步方法
同步方法是使用synchronized关键字修饰需要同步的方法其格式如下:
[访问修饰符] synchronized 返回类型 方法名([参数列表]){
//方法体
}
案例解决代码
public class safeBuyTicket {public static void main(String[] args) {
BuyTicket2 buyTicket=new BuyTicket2();
Thread thread=new Thread(buyTicket,"小张");
Thread thread2=new Thread(buyTicket,"小红");
Thread thread3=new Thread(buyTicket,"小李");
//thread.setPriority(1);
thread.start();
thread2.start();
thread3.start();
}
}
class BuyTicket2 implements Runnable{
private int ticketNums=10;
boolean flag=true;//线程停止方式使用外部停止方式
@Override
public void run() {
while (flag){
ButTicket();
}
}
public synchronized void ButTicket(){
if(ticketNums<=0){
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNums--+"张票");
}
}
同步代码块
使用同步代码块实现同步功能,只需要将对实例的访问语句放入一个同步块中,其语法格式如下:
//要千万注意,要锁对对象才能保证线程安全
synchronized(object){
//所需要同步的代码块
}
同步代码块使用案例
package Thread;
public class SynBlockBank extends Thread {
//银行账户
private BankAccount account;
//操作金额,正数为存钱,负数为取钱
private double money;
public SynBlockBank(String name,BankAccount account,double money){
super(name);
this.account=account;
this.money=money;
}
@Override
public void run() {
synchronized (account){
double d=this.account.getBalance();
if(money<0&&d<-money){
System.out.println(this.getName()+"操作失败,余额不足!");
return;
}else {
d+=money;
System.out.println(this.getName()+"操作成功,目前账户余额为:"+d);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setBalance(d);
}
}
}
public static void main(String[] args) {
BankAccount bankAccount=new BankAccount("2020520",5000);
SynBlockBank t1=new SynBlockBank("T001",bankAccount,-3000);
SynBlockBank t2=new SynBlockBank("T002",bankAccount,-3000);
SynBlockBank t3=new SynBlockBank("T003",bankAccount,1000);
SynBlockBank t4=new SynBlockBank("T004",bankAccount,-2000);
SynBlockBank t5=new SynBlockBank("T005",bankAccount,2000);
t1.start(); //启动线程
t2.start(); //启动线程
t3.start(); //启动线程
t4.start(); //启动线程
t5.start(); //启动线程
try{
//等待所以子线程完成
t1.join();t2.join();t3.join();t4.join();t5.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("账户"+bankAccount.getBankNo()+"最终余额为:"+bankAccount.getBalance());
}
}
class BankAccount {
private String bankNo;
private double balance;
public BankAccount(String bankNo, double balance) {
this.bankNo = bankNo;
this.balance = balance;
}
public String getBankNo() {
return bankNo;
}
public void setBankNo(String bankNo) {
this.bankNo = bankNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
死锁产生的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资料保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
那么如何避免死锁呢?-我们只有想办法破坏其中的任意一个或者多个条件就可以避免死锁发生。
同步锁(Lock锁)
原理与同步代码块是一样的,只不过是加锁方法和形式不一样而已,看一个案例就可以理解了
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
BuyTicket3 buyTicket=new BuyTicket3();
Thread thread=new Thread(buyTicket,"小张");
Thread thread2=new Thread(buyTicket,"小红");
Thread thread3=new Thread(buyTicket,"小李");
thread.start();
thread2.start();
thread3.start();
}
}
class BuyTicket3 implements Runnable{
private int ticketNums=10;
boolean flag=true;//线程停止方式使用外部停止方式
private final ReentrantLock lock=new ReentrantLock();//定义Lock锁
@Override
public void run() {
while (flag){
ButTicket();
}
}
public void ButTicket(){
try{
lock.lock();//加锁
if(ticketNums<=0){
flag=false;
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticketNums--+"张票");
}finally {
lock.unlock();//解锁
}
}
}
线程通信
Java中提供了一些机制来保证线程之间的协调运行,这就是所谓的线程通信,
需求假设 : 目前有系统有生产和消费两个线程,系统要求不断重复生产,消费操作,并要求每当一个线程生产后,另一个线程立即进行消费,不允许连续两次生产,也不允许连续两次消费。为了实现这种功能,可以采用线程之间的通信技术。
线程通信可以使用Object类中定义的wait(),notify(),和notifyAll()方法,使线程之间互相进行事件通知。执行这些方法时,必须拥有相关对象的锁。
- wait()方法,让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或notifyAll()来唤醒该线程。wait()方法也可以带一个参数,用于指名等待的时间,使用此种方式不需要notify()或notifyAll()的唤醒。wait()方法只能在一个同步方法中调用。
- notify()方法:唤醒在此同步监视器上等待的单个线程,解除该线程的阻塞状态。
- notifyAll()方法:唤醒在此同步监视器上等待的所有线程,唤醒次序完全由系统来控制。
注意 : notify()和notifyAll()方法只能在同步方法或者同步块中使用。wait()方法区别于sleep()方法之处:wait()方法调用时会释放对象锁,而sleep()方法不会。
代码通过生产/消费模型演示线程通信机制的应用
package Thread;
public class WaitDemo {
public static void main(String[] args) {
//实例化一个产品对象,生产者和消费者共享该实例
Product product=new Product();
//指定生产线程
Producer producer=new Producer(product);
//指定消费线程
Consumer consumer=new Consumer(product);
}
}
class Product{
int n;
//为true时表示有值可取,为false时表示需要放入新值
boolean valueSet=false;
//生产方法
synchronized void put(int n){
if (valueSet){
try{
wait();
}catch (Exception e){
}
}
this.n=n;
//将valueSet设置为true,表示值已经放入
valueSet=true;
System.out.println(Thread.currentThread().getName()+"-生产:"+n);
notify();
}
//消费方法
synchronized void get(){
if(!valueSet){
try{
wait();
}catch (Exception e){
}
}
System.out.println(Thread.currentThread().getName()+"-消费:"+n);
valueSet=false;
notify();
}
}
//生产者
class Producer implements Runnable{
Product product;
public Producer(Product product){
this.product=product;
new Thread(this,"Producer").start();
}
@Override
public void run() {
int k=0;
//生产10次
for (int i = 0; i < 10; i++) {
product.put(k++);
}
}
}
//消费者
class Consumer implements Runnable{
Product product;
public Consumer(Product product){
this.product=product;
new Thread(this,"Consumer").start();
}
@Override
public void run() {
//消费10次
for (int i = 0; i < 10; i++) {
product.get();
}
}
}
执行结果如下:
线程通信案例2
代码:
public class Demo06 {
static Object object = new Object();
public static class T1 extends Thread{
@Override
public void run(){
synchronized (object){
System.out.println("T1 start!");
try{
System.out.println("T1 wait for object");
object.wait();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("T1 end");
}
}
}
public static class T2 extends Thread{
@Override
public void run(){
synchronized (object){
System.out.println("T2 start, notify one thread");
object.notify();
System.out.println("T2 end!");
try{
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
new T1().start();
new T2().start();
}
}
执行结果如下:
注意:注意下打印结果,T2调用notify方法之后,T1并不能立即继续执行,而是要等待T2释放objec投递锁之后,T1重新成功获取锁后,才能继续执行。因此最后2行日志相差了2秒(因为T2调用notify方法后休眠了2秒)。