java多线程(中)

线程同步:

多线程编程三大问题:

分工:任务拆分,哪些线程干哪些任务

同步:多个线程一起做一件事情,线程间通信(线程合作)

互斥:多线程并发时,只能有一个线程访问共享资源

 

问题引入(一张票多次卖出问题):

还剩一张票     //线程1

还剩-1张票     //线程2

还剩0张票      //线程3

public void run() {
        //ticket =1 ;
        while (this.ticket > 0) {
            //线程1 2 3
            try {
                //线程2
                //线程3
                //线程1
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程1 ticket = 0;
            //线程1 ticket = -1;
            //线程1 ticket = -2;
            System.out.println("还剩下"+ticket--+"票");
        }

    }

 

锁:

一把锁保护一个相应的资源,不同锁保护不同的对象

java中锁的实现:

使用sychroinzed关键字为程序逻辑上锁

两种用法:

(1)同步代码块

sychroinzed(锁的对象(this,也就是当前对象)|Object及其子类|类对象(当前类.class)) {

  //此处代码块在任意一个时刻只能有一个线程进入

}

class TicketTask implements Runnable {
    private int ticket = 20;
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //需要在判断处上锁
                synchronized (this){
                    //在任意时刻只有一个线程能进入条件判断
                    if (ticket >0) {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+
                                "还剩下"+ticket--+"票");
                    }
                }
        }
    }
}
public class SellTicket {
    public static void main(String[]args){
        TicketTask ticketTask = new TicketTask();
        Thread thread1 = new Thread(ticketTask,"黄牛A");
        Thread thread2 = new Thread(ticketTask,"黄牛B");
        Thread thread3 = new Thread(ticketTask,"黄牛C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

(2)同步方法

(1)修饰普通对象方法 public synchronized void sellTicket() ; 默认锁的是当前对象this

直接在方法声明上使用sychroinzed,此时表示同步方法在任一时刻只能有一个线程进入

class TicketTask implements Runnable {
    private int ticket = 20;
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
                sellTicket();
        }
    }
    //在任一时刻,只能有一个线程进入此方法
    public synchronized void sellTicket() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName()+
                    "还剩下"+ticket--+"票");
        }

    }
}
public class SellTicket {
    public static void main(String[]args){
        TicketTask ticketTask = new TicketTask();
        Thread thread1 = new Thread(ticketTask,"黄牛A");
        Thread thread2 = new Thread(ticketTask,"黄牛B");
        Thread thread3 = new Thread(ticketTask,"黄牛C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

 

sychroinzed对象锁

class Sync {
    public synchronized void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"正在打印...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"执行结束...");
    }
}
class Task implements Runnable{
    Sync sync = new Sync(); //改进方法一
    @Override
    public void run() {
        //此处的synchronized并不能锁住,因为会产生三个Sync对象,也就是三把锁
       // Sync sync = new Sync();
        try {
            sync.test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class SyncTest {
    public static void main(String[]args){
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

(2)修饰的是静态方法,锁当前类.class

若 synchronized 修饰的是静态方法或者synchronized (类名称.class) synchronized 锁的都是当前类的反射对象(全局唯一)

class Sync {
    //改进方法二
    public static synchronized void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"正在打印...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"执行结束...");
    }
}
class Task implements Runnable{
    @Override
    public void run() {
        Sync sync = new Sync();
        try {
            sync.test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class SyncTest {
    public static void main(String[]args){
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}
class Sync {
    //同一时刻,线程一进入test方法,别的线程并不能进入test2,此时同一把锁保护了两个方法
    public  synchronized void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"正在打印...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"执行结束...");
    }
    public synchronized void test2() {}
}
class Task implements Runnable{
    Sync sync = new Sync();
    @Override
    public void run() {
        try {
            sync.test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class SyncTest {
    public static void main(String[]args){
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

 

synchronized 底层实现:

在使用 synchronized 时必须保证锁定对象必须为Object及其子类对象

synchronized 使用的是JVM层级别的Monitor与MonitorExit实现

这两个指令必须获取对象的同步监视器Monitor

 

对象Monitor机制:
第一步:monitorenter:

检查O对象的Monitor计数器值是否为0,为0表示此监视器还未被任意一个线程获取,此线程可以进入同步代码块并且将Monitor值+1,将monitor的持有线程标记为当前线程。

情况1:当Monitor计数器值不为0且持有线程不是当前线程,表示Monitor已被别的线程占用,当前线程只能阻塞等待。

情况2:当Monitor计数器值不为0但是持有的线程恰好是当前线程,

线程2

 第二步(进入同步代码块):    synchronized(O) {

//线程1

    }

第三步(退出):monitorexit (线程释放 monitor计数器-1)

可重入锁:

当执行MonitorEnter时,对象的Monitor计数器值不为0,但是持有的线程恰好是当前线程,此时将Monitor计数器的值再次+1,当前线程继续进入同步方法或代码块。

synchronized void TestA(){
          //线程A
         //TestB()  这里就相当于可重入锁,可以进入
}

synchronized void TestB(){
          //线程B能否同时进入同一个对象的TestB();  (不能)
}

 

JDK1.6 之后synchronized 优化(了解)

synchronized 互斥,

之前synchronized 获取锁失败,将线程挂起—悲观锁策略

优化让每个线程通过代码块的速度提高

1、CAS操作(无锁实现的同步-乐观锁)—自旋:

class Test {
  int i = 0;
  synchronized (this) {
      i = 10;
    }
}

compareAndSwap(O,V,N)

O:当前线程存储的变量值  0

V:内存中该变量的具体指   10

N:希望修改后的变量值        10

当O==V时,此时表示还没有线程修改共享变量的值,此时可以成功的将内存中的值修改为N

当O!= V时,表示此时内存中的共享变量值已被其他线程修改,此时返回内存中最新值V,再次尝试修改变量

线程挂起阻塞:车熄火

自旋:脚踩刹车,不熄火

导致的问题:

(1)ABA问题:解决方法:添加版本号

(2)自旋在CPU上跑无用指令,会浪费CPU资源,

自旋适应:JVM尝试自旋一段时间,若在时间内成功获取到锁,在下次获取锁时,适当延长自旋时间

若在时间内,线程没有获取到锁,在下次获取时,适当缩短自旋时间

(3)公平性问题:处于阻塞状态线程可能会一直无法获取到锁

  Lock锁可以实现公平性,synchronized 无法实现公平锁

2、偏向锁:JDK1.6之后默认synchronized

最乐观的锁,进入同步代码块或者方法的始终是一个线程

当出现另一个线程也尝试获取锁(在不同时刻)时,偏向锁会升级为轻量级锁

3、轻量级锁:不同的时刻有不同的线程尝试获取锁,“亮黄灯策略”

同一时刻不同线程尝试获取锁,会将偏向锁升级为重量级锁

4、重量级锁 :JDK1.6之前的synchronized 都是重量级锁,将线程阻塞挂起(JDK1.6自适应自旋)

锁只有升级过程没有降级过程

5、锁粗化:当出现多次的加锁与解锁过程,会将多次的加锁解锁过程粗化为一次的加锁与解锁过程。

public class Test{
    private static StringBuffer sb = new StringBuffer();
    public static void main(String[] args) {
        sb.append("a");
        sb.append("b");
        sb.append("c");
       }
}

6、锁消除

当对象不属于共享资源时,对象内部的同步方法或同步代码块会被自动解除

锁消除即即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那

么可以认为这段代码是线程安全的,不必要加锁。

public class Test{
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("a").append("b").append("c");
   }
}

虽然StringBufferappend是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中

逃逸出去,所以其实这过程是线程安全的,可以将锁消除。

 

 

 

 

 

 

你可能感兴趣的:(JAVA学习)