控制线程与线程同步
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线程:砌完这次墙了墙高:2米
1线程:砌完这次墙了墙高:2米
2线程:砌完这次墙了墙高:4米
1线程:砌完这次墙了墙高:4米
2线程:砌完这次墙了墙高:6米
1线程:砌完这次墙了墙高:6米
2线程:砌完这次墙了墙高:8米
1线程:砌完这次墙了墙高:8米
2线程:砌完这次墙了墙高:10米
1线程:砌完这次墙了墙高:10米
上述运行结果每次不一样,加sleep就是为了更好的展现问题,如果不加sleep,有很多的时候发现不了开发的代码中的安全问题.
那么如何让线程变得安全?那就是让方法同步,对方法加锁,只允许一个线程执行同步方法,其他线程等待当前线程执行完后才能去尝试获取执行权.
synchronized代码块
同步代码块
synchronized(obj){
//逻辑
}
obj是同步监视器(锁),就是共享的要修改的对象,只有获得这个对象,才允许执行代码块中的内容,这样,线程1获取到了obj,就会锁住obj,线程1执行代码块中的代码,线程2就会在同步代码块外等待,直到获取到obj才会执行,这样就防止了同步代码块中共同操作造成的不安全
去掉上方代码中的同步代码块注释的同步代码块,就会发现结果是我们想要的:
1线程:砌完这次墙了墙高:2米
1线程:砌完这次墙了墙高:4米
2线程:砌完这次墙了墙高:6米
2线程:砌完这次墙了墙高:8米
2线程:砌完这次墙了墙高:10米
2线程:砌完这次墙了墙高:12米
2线程:砌完这次墙了墙高:14米
1线程:砌完这次墙了墙高:16米
1线程:砌完这次墙了墙高:18米
1线程:砌完这次墙了墙高: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线程:砌完这次墙了墙高:2米
2线程:砌完这次墙了墙高:4米
2线程:砌完这次墙了墙高:6米
2线程:砌完这次墙了墙高:8米
2线程:砌完这次墙了墙高:10米
2线程:砌完这次墙了墙高:12米
1线程:砌完这次墙了墙高:14米
1线程:砌完这次墙了墙高:16米
1线程:砌完这次墙了墙高:18米
1线程:砌完这次墙了墙高: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线程:砌完这次墙了墙高:2米
2线程:砌完这次墙了墙高:4米
2线程:砌完这次墙了墙高:6米
2线程:砌完这次墙了墙高:8米
2线程:砌完这次墙了墙高:10米
1线程:砌完这次墙了墙高:12米
1线程:砌完这次墙了墙高:14米
1线程:砌完这次墙了墙高:16米
1线程:砌完这次墙了墙高:18米
1线程:砌完这次墙了墙高: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.