1.同一个对象被多个线程操纵
2.处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改该对象,这时就需要线程同步。线程同步实际上就是一种等待机制,多个需要同时访问该对象的线程进入这个对象的线程池形成队列,等待前面线程执行完毕,下一个线程再继续使用。
3.在对象线程池里存在着队列和锁,队列则是等待操作该对象的线程队伍,锁则是当该对象在被线程操作时,其他线程无法操作该对象,锁机制synchronized,所以当线程获得对象的排他锁,独占资源,使用后释放即可;其他线程必须等待,但是会存在以下问题:
3.1一个线程持有锁会导致其他需要此锁的线程挂起。
3.2多个线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3.3若优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
1.synchronized方法(public synchronized void method (int args){})
就是在将买票的方法buy,加上关键字synchronized
//以买火车票为例子
//多个线程操控同一个资源的情况下,线程不安全,数据紊乱
public class ManyOpOne implements Runnable{
//票数
private int ticketNum = 5;
boolean flag = true;
//synchronized 同步方法,锁的是this,也就是当前对象。
public synchronized void buy(){
//判断是否有票
if(ticketNum <= 0){
flag = false;
return;
}
//买票
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "票");//获取线程名字,知道是哪个线程拿的票
}
@Override
public void run() {
while(flag){
buy();
}
}
public static void main(String[] args) {
ManyOpOne manyOpOne1 = new ManyOpOne();
//每个线程操作的是同一个ManyOpOne对象,所以其中成员变量ticketNum是共用的,但是每个线程的Run方法是独立的,可通过共同类对象的成员变量来控制不同的线程
new Thread(manyOpOne1,"小明").start();
new Thread(manyOpOne1,"小李").start();
new Thread(manyOpOne1,"小张").start();
}
}
不加同步方法的运行结果:当三给线程去买票的时候,都是5张票,从而造成混乱
加同步方法的运行结果:队列+锁,一个线程一个线程的来
2.synchronized方法控制对象的访问:
synchronized方法都必须调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面的线程才可以活得该锁,继续执行。
同步块用法:1.synchronized(Obj){}。
2.Obj可以是如何对象,但是推荐使用共享资源作为同步监视器,当一个线程访问同步监视器,执行其中代码,则同步监视器将被锁定,无法被其他线程访问。
3.同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是该class。
以两个人去银行取钱为例,两个人同时去到银行,同时操作账户,则会发生两个人看到的余额都是一样的,操作时也会操作一样的账户余额,所以synchronized ()中所要锁的则是账户这个对象,而不是方法。(如果第一种方法的案例要用同步块,则在run方法下面将this对象作为同步监视器对象即可)
//案例:两个人去银行取钱
public class ManyOpOne02 {
public static void main(String[] args) {
//账户
Accout account = new Accout(1000, "零花钱");
Drawing you = new Drawing(account, 50, "本人");
Drawing lover = new Drawing(account, 100, "你对象");
you.start();
lover.start();
}
}
class Drawing extends Thread{
Accout account;//账户
//取了多少钱
int drawingMoney ;
//现在手里有多少钱
int nowmoney;
public Drawing(Accout account,int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//将账户这个对象放进同步块
synchronized (account){
//判断有没有钱
if (account.money - drawingMoney < 0){
System.out.println(account.name + "钱不够");
return;
}
//卡内的钱 = 余额 - 取出的钱
account.money = account.money - drawingMoney;
//手里的钱 = 原来手里有的钱 + 取出的钱
nowmoney = nowmoney + drawingMoney;
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName() + "手里的钱" + nowmoney);
System.out.println(account.name + "余额为" + account.money);
}
}
}
class Accout{
int money;//金额
String name;//名字
public Accout(int money,String name ) {
this.money = money;
this.name = name;
}
}
不加synchronized块的运行结果:由于是两个线程一起操作该对象,则顺序紊乱
加synchronized块的运行结果:一个线程一个线程的对对象进行操作:
由于普通ArrayList存在线程安全问题,需要用同步块去解决,则JDK内部有安全类型集合CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList;
//测试安全集合JUC
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
//添加10000个线程到该集合
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
//延长时间让线程执行完毕
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
运行结果则是10000;如果是ArrayList的话,运行结果不到10000。