java多线程(二) 控制线程与线程同步

控制线程与线程同步

java多线程(一) 基础理论与执行状态
java多线程(二) 控制线程与线程同步
java多线程(三) 线程通信与线程池

*控制线程

对于线程的控制:
join()等待线程完成
sleep() 线程主动进入阻塞态
yeild() 线程让步
后台线程

join()

public  class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("2");
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1");
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.join();                //1
        System.out.println("3");
    }
}

上面代码中start()后调用了mythred的join()方法.join()方法的作用是告诉调用它的线程(这里是main线程)要等待mythread线程执行完成后再继续执行,如果没有join()方法的话,main线程后的System.out.println(“3”);会与mythread的run方法并发执行

sleep()

public  class MyThread extends Thread {
    @Override
    public void run() {
        try {
            Thread. sleep(1000);   //1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("2");
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1");
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(1000);      //2
        System.out.println("3");
    }
}

上方调用了Thread的sleep()方法,这个方法是使当前调用的线程,主动进入到阻塞状态多少秒,时间结束就进入就绪态,相信很多人都用过,所以不多说了

yeild()

public  class MyThread extends Thread {
    @Override
    public void run() {
       //逻辑
        yield();
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

yield跟sleep有点像,yield()方法不会使调用它的线程进入阻塞态,而是直接进入就绪态,等待jvm调度器调度运行,以让比它优先级相同或者更高的线程获得执行机会.也很有可能进入就绪状态后,马上jvm就调度他继续运行了.

线程优先级

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.setPriority(Thread.MAX_PRIORITY); //权限最高
       // myThread.setPriority(Thread.NORM_PRIORITY); 权限其次
       // myThread.setPriority(Thread.MIN_PRIORITY);  权限最低
        myThread.start();
    }

这里的优先级入参是一个int型整数,1-10,但是因为系统不一样可能会出现问题,所以建议使用Thread类中定义好的常量.另外,子线程的优先级不指定的情况下与创造它的父线程相同,main线程的优先级是5也就是NORM_PRIORITY

后台线程

再说一下后台线程,后台线程是我们常听到的守护线程,他是为其他线程服务的,所有的前台线程死亡后,后台进程也会死亡.

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.setDaemon(true); //设置线程为后台线程
        myThread.start();
    }

*线程同步

线程同步主要是为了安全性,比如砌墙,墙要求20米高,一块砖2米,两个人去砌墙,无论谁砌墙都要把先看本子上记录的当前高度,然后砌完一块砖后,更新本子上的高度.如果现在砌墙到了18米还剩下最后一块,这时候轮到甲去砌墙,甲知道高度后,去砌墙,然后要去更新本子上高度为20米,此时乙刚好在甲更新本子之前,看了本子上的高度18米,不够20米,而实际上甲已经把墙砌到了20米,这个时候乙再去砌一块砖,墙就变成了22米.这个比喻就是线程不安全的地方
下面是线程不安全的代码,两个线程总共砌墙10次,应该砌墙高度为20米,但是却发现结果并不是20米

public class WallHigh {
    private int high = 0;
   
    public int getHigh() { return high;}

    public void setHigh(int high) { this.high = high;}   
}

public class MyThread extends Thread {
    private WallHigh wallHigh;

    public MyThread(WallHigh wallHigh) {this.wallHigh = wallHigh; }

    public void wall() throws InterruptedException {
       // synchronized (wallHigh) {
            int high = wallHigh.getHigh();
            Thread.sleep(10);
            if (high < 20) {
                Thread.sleep(10);
                wallHigh.setHigh(high + 2);
                Thread.sleep(10);
                System.out.println(getName() + ":砌完这次墙了墙高:" + (high + 2) + "米");//getName()获取当前线程的名字
            } else if (high == 20) {
                System.out.println("砌完20米的墙了" + wallHigh.getHigh());
            }
       // }
    }

    @Override
    public void run() {

        for (int i = 1; i <= 5; i++) {
            try {
                wall();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WallHigh wallHigh = new WallHigh();
        MyThread myThread1 = new MyThread(wallHigh);
        myThread1.setName("1线程");
        MyThread myThread2 = new MyThread(wallHigh);
        myThread2.setName("2线程");
        myThread1.start();
        myThread2.start();
    }
}
运行结果:
2线程:砌完这次墙了墙高:21线程:砌完这次墙了墙高:22线程:砌完这次墙了墙高:41线程:砌完这次墙了墙高:42线程:砌完这次墙了墙高:61线程:砌完这次墙了墙高:62线程:砌完这次墙了墙高:81线程:砌完这次墙了墙高:82线程:砌完这次墙了墙高:101线程:砌完这次墙了墙高:10

上述运行结果每次不一样,加sleep就是为了更好的展现问题,如果不加sleep,有很多的时候发现不了开发的代码中的安全问题.
那么如何让线程变得安全?那就是让方法同步,对方法加锁,只允许一个线程执行同步方法,其他线程等待当前线程执行完后才能去尝试获取执行权.

synchronized代码块

同步代码块

synchronized(obj){
//逻辑
}

obj是同步监视器(锁),就是共享的要修改的对象,只有获得这个对象,才允许执行代码块中的内容,这样,线程1获取到了obj,就会锁住obj,线程1执行代码块中的代码,线程2就会在同步代码块外等待,直到获取到obj才会执行,这样就防止了同步代码块中共同操作造成的不安全
去掉上方代码中的同步代码块注释的同步代码块,就会发现结果是我们想要的:

1线程:砌完这次墙了墙高:21线程:砌完这次墙了墙高:42线程:砌完这次墙了墙高:62线程:砌完这次墙了墙高:82线程:砌完这次墙了墙高:102线程:砌完这次墙了墙高:122线程:砌完这次墙了墙高:141线程:砌完这次墙了墙高:161线程:砌完这次墙了墙高:181线程:砌完这次墙了墙高:20

synchronized同步方法

上方代码使用了同步代码块,我们可以用同步方法来替代代码块

 public synchronized void wall(){}

但是同步代码块中的同步监视器(锁)是我们指定的,我们可以指定MyThread类中变量wallHigh,而同步方法中的同步监视器(锁)是this,也就是当前对象本身,所以我们要用同步方法,首先要考虑把方法写到同步监视器(锁)中 也就是WallHigh类中
下面就是代码

public class WallHigh {
    private int high = 0;

    public int getHigh() { return high;}

    public void setHigh(int high) { this.high = high;}
    
    public synchronized void  wall( ) throws InterruptedException {
        Thread.sleep(10);
        if (high < 20) {
            Thread.sleep(10);
            high+=2;
            Thread.sleep(10);
            System.out.println(Thread.currentThread().getName() + ":砌完这次墙了墙高:" + high  + "米");//getName()获取当前线程的名字
        } else if (high == 20) {
            System.out.println("砌完20米的墙了" + high);
        }
    }
}

public class MyThread extends Thread {
    private WallHigh wallHigh;

    public MyThread(WallHigh wallHigh) { this.wallHigh = wallHigh;}
    
    @Override
    public void run() {

        for (int i = 1; i <= 5; i++) {
            try {
                wallHigh.wall();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)   {
        WallHigh wallHigh = new WallHigh();
        MyThread myThread1 = new MyThread(wallHigh);
        myThread1.setName("1线程");
        MyThread myThread2 = new MyThread(wallHigh);
        myThread2.setName("2线程");
        myThread1.start();
        myThread2.start();
    }
}
运行结果:
1线程:砌完这次墙了墙高:22线程:砌完这次墙了墙高:42线程:砌完这次墙了墙高:62线程:砌完这次墙了墙高:82线程:砌完这次墙了墙高:102线程:砌完这次墙了墙高:121线程:砌完这次墙了墙高:141线程:砌完这次墙了墙高:161线程:砌完这次墙了墙高:181线程:砌完这次墙了墙高:20

关于锁,可以这样想象,有个展会必须持展览证才能进去,如果有两人,他们有两个展览证(同步监视器不共享),那么同一时间,两个人都可以进到展会,但是如果只有一个展览证(同步监视器共享),同一时间就只能有一个人能拿到展览证进到展览会,等另外一个人出来后,另外一个人才有可能获得展览证的机会.
所以同步监视(锁)必须是共享的,只有这样,同步代码块或者同步方法才有意义.
我们一般遵循的原则是 加锁(一般用要修改的共享变量)->修改->释放锁.
同步方法跟同步代码块,只是为了让线程在处理的时候,同一时间只允许一个线程处理,保证安全问题,不要把同步方法代码与线程类代码绑定到一起,线程类与同步方法并没有代码上的依赖关系.

Lock同步锁

synchronized同步代码块和同步方法称为隐式锁,因为并没有添加加锁跟释放锁的操作 而lock显式锁,可以显式的添加加锁跟解锁
Lock是一个接口,有很多实现类,最常用的是ReentrentLock.
下面改造WallHigh类

public class WallHigh {
    private int high = 0;
    
    private ReentrantLock lock = new ReentrantLock();    1

    public int getHigh() { return high; }

    public void setHigh(int high) { this.high = high; }

    public void wall() throws InterruptedException {
        lock.lock();                                     2
        try {
            Thread.sleep(10);
            if (high < 20) {
                Thread.sleep(10);
                high += 2;
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + ":砌完这次墙了墙高:" + high + "米");//getName()获取当前线程的名字
            } else if (high == 20) {
                System.out.println("砌完20米的墙了" + high);
            }
        }finally {
            lock.unlock();                               3
        }
    }
}
运行结果:
2线程:砌完这次墙了墙高:22线程:砌完这次墙了墙高:42线程:砌完这次墙了墙高:62线程:砌完这次墙了墙高:82线程:砌完这次墙了墙高:101线程:砌完这次墙了墙高:121线程:砌完这次墙了墙高:141线程:砌完这次墙了墙高:161线程:砌完这次墙了墙高:181线程:砌完这次墙了墙高:20

代码1的地方定义了一个锁对象,代码2处是加锁,最后finally里面释放锁,注意,要不然处理加锁跟解锁代码中间可能出现的所有异常再释放锁,要不然就在finally中释放锁
synchronized必须在一个块儿中(代码块或者方法块),而显示锁的方式相比较于隐式锁更加灵活,可以决定加锁与释放锁的位置.

死锁

死锁就是两个线程一直在等待对方释放各自持有的加锁对象,比如Athread 持有A对象,等待获取B对象,而BThread持有B对象,等待获取A对象,因为相互持有对方需要的对象,就会导致两个线程一直等待下去,开发中如果有死锁会导致一直等待,而不会有任何异常,要排查问题比较麻烦,所以一定要避免死锁.

定义两个类A和B,用来当锁(也可以用Class,但是这里用AB更好看)
public class A {
}

public class B {
}
定义两个线程AThread和BThread
public class AThread extends Thread {
    private A a;
    private B b;
    public AThread(A a, B b){
        this.a=a;
        this.b=b;
    }
    @Override
    public void run() {
        synchronized (a){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("AThread获取到了a");
            synchronized (b){
                System.out.println("AThread永远进不了的地方");
            }
        }
    }
}

public class BThread extends Thread{
    private A a;
    private B b;

    public BThread(A a, B b){
        this.a=a;
        this.b=b;
    }
    @Override
    public void run() {
        synchronized (b){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("BThread获取到了b");
            synchronized (a){
                System.out.println("BThread永远到不了的地方");
            }
        }
    }

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        new AThread(a,b).start();
        new BThread(a,b).start();
    }
}
运行结果:
AThread获取到了a
BThread获取到了b
(这个地方是线程一直等待下去....)

ThreadLocal

**线程同步是为了使操作共有变量变得安全,但是ThreadLocal是为了隔离变量,使变量不共享.
举个例子,如果我们要共同操作一个变量,那么就用共享变量,如果要各自操作不同变量,那么就不用共享变量,但是如果要一个类中的一个变量共享,一个变量不共享呢?要共享,就让线程共享一个实例,不共享,就每个线程一个实例.ThreadLocal可以解决这个问题,只需要,在类中定义一个ThreadLocal成员变量,那么这个ThreadLocal变量每个线程都有一份,互不干扰.
**

public class ThreadLocalTest {

    private ThreadLocal<Integer> noShareVar = new ThreadLocal<>();

    private Integer shareVar ;

    public ThreadLocalTest(Integer noShareVar, Integer shareVar) {
        this.noShareVar.set(noShareVar);
        this.shareVar = shareVar;
    }

    public Integer getNoShareVar() {
        return noShareVar.get();
    }

    public void setNoShareVar(Integer noShareVar) {
        this.noShareVar.set(noShareVar);
    }

    public Integer getShareVar() {
        return shareVar;
    }

    public void setShareVar(Integer shareVar) {
        this.shareVar = shareVar;
    }

}


public class MyThread4 extends Thread {
    private ThreadLocalTest threadLocalTest;

    public MyThread4(ThreadLocalTest threadLocalTest) {
        this.threadLocalTest = threadLocalTest;
    }

    public void run() {
        synchronized (threadLocalTest) {
            System.out.println(getName()+"  noShareVar:"+threadLocalTest.getNoShareVar());
            System.out.println(getName()+"  shareVar:"+threadLocalTest.getShareVar());
        }
    }

    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest(0, 0);
        MyThread4 myThread1 = new MyThread4(threadLocalTest);
        MyThread4 myThread2 = new MyThread4(threadLocalTest);
        myThread1.setName("A");
        myThread2.setName("B");
        myThread1.start();
        myThread2.start();
        System.out.println(Thread.currentThread().getName()+"  noShareVar:"+threadLocalTest.getNoShareVar());
        System.out.println(Thread.currentThread().getName()+"  shareVar:"+threadLocalTest.getShareVar());
    }
}
运行结果:
main  noShareVar:0
main  shareVar:0
A  noShareVar:null
A  shareVar:0
B  noShareVar:null
B  shareVar:0

因为初始化ThreadTest是在main线程中,给变量ThreadLocal添加了0,但是在A跟B线程中并没有给它赋值,所以A跟B中的noShareVar中并没有值,这里就实现了,一个变量共享shareVar,一个变量线程不共享noShareVar.

下一篇 java多线程(三) 线程通信与线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值