线程-一

[TOC]

线程基础

线程状态

线程的状态又分为五种:

  • New:新建状态
  • Runnable:就绪状态
  • Running:运行状态
  • Blocked:阻塞状态
  • Dead:死亡状态
1
2
3
4
5
6
7
graph TD
New --> Runnable
Runnable-->Running
Blocked-->Runnable
Running-->Runnable
Running-->Blocked
Running-->Dead

线程方法

interrupted()

判断当前线程是否是中断、停止状态,执行后将状态标志清除为false,静态方法。可以直接调用

isInterrupted()

判断当前线程是否是中断、停止状态。但不清除状态标志。非静态方法。

interrupt()

将状态标记为中断,用于线程退出。在run方法中使用interrupted判断线程状态。如果返回truereturn或抛出异常。

yield()

将Running状态转变为Runnable状态。把线程CPU让给其他线程,让出时间不确定,有可能上一秒让出下一秒又获取到。

sleep()

使线程休眠多少毫秒,参数为long ms

join()

join方法的作用是父线程等待子线程执行完成后再执行。换句话说就是将异步执行的线程合并为同步的线程。

wait/notify/notifyAll方法

这些方法并不是Thread类中的。而是Object类的方法。不过这些也是学习线程中不可或缺的。

wait()

wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程。此方法需要与锁一起使用。

notify

与使用wait的同一对象使用该方法即可唤醒处于等待的线程。

notifyAll

notify方法差不多,只不过notify只唤醒当前线程。而notifyAll则唤醒所有线程。

线程优先级

在线程中可以通过setPriority(int) 来设置线程优先级。子类的优先级与父类的优先级一致。
线程优先级从低到高为1~10。超过则抛出IllegalArgumentException()异常
且线程优先级不代表一定会按照顺序执行。

守护线程

在Java线程中有两种线程,一种是用户线程,另一种是守护线程。
一旦用户线程全都执行完毕之后,守护线程也会结束。没有用户线程时守护进程将自动销毁。
线程可以在执行start()方法之前执行setDaemon(true) 变成守护进程。

对象及变量并发访问

当多个线程对一个对象实例或者一个对象实例变量访问的时候就有可能出现线程安全。

方法内的变量为线程安全

所有线程内部的私有变量都不会引起线程安全

synchronized

synchronized所声明的方法为线程安全。
synchronized 使用:
synchronized可以在方法签名中声明。也可以声明代码块。

1
2
3
4
5
6
public synchronized void mothed(){
...
}
synchronized(Obj){
...
}

最好不要使用String,或其他基本数据类型作为synchronized所修饰的对象。因为

1
2
String a1="aa";
String a2="aa";

由于常量池的原因。a1=a2。所以在synchronized(String)中会被当成同一个对象。
使用synchronized之后,异步方法会进行排队。可以看作原子级的方法。
只有共享变量/实例才需要同步。如果不是共享变量或实例则不需要同步。

synchronized 重入

synchronized声明的代码块,方法来说都具有重入的特性,也就是说synchronized代码块或方法可以调用同一个对象的synchronized而不会产生死锁。

synchronized 同步代码块

使用synchronized声明一个同步代码块比直接声明一个方法性能要好很多。同时使用一个对象实例比this效率要高一些。使用synchronized(非this)代码块中的程序与其他同步方法是异步的。因为他锁的对象不是this,如果需要同步的话需要锁住的对象是同一个。
注意:同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行顺序。所以非常容易产生“脏读”的问题。
也就是说synchronized修饰方法时,锁住的对象是this。所以synchronized修饰的方法与synchronized(非this)的代码块之间非常容易出问题。

synchronized静态同步

synchronized可以用到静态方法上,这样写是对当前*.java文件对应的Class类进行加锁。synchronized加到静态方法上是对Class加锁。synchronized加到非静态方法上是给对象加锁。如果有一个静态方法。同时有两个实例调用此方法。则该方法为同步方法。
synchronized(Object.class)等于synchronized声明静态方法。

锁对象改变

在同步代码块中如果锁对象发生了改变,会发生什么呢?锁对象还有用吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class test {
public static void main(String[] args) {
Test1 test1 = new Test1();
new Thread(() -> {
try {
test1.ts(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A1").start();
new Thread(() -> {
try {
test1.ts(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A2").start();
}
}

class Test1 {
private String lock = "a1";

public void ts(String name) throws InterruptedException {
synchronized (lock) {
System.out.println(name + ":start");
lock = "a2";
Thread.sleep(1000);
System.out.println(name + ":end");
}
}
}

真实情况是:一旦锁发生了改变,且在改变之后又有新的线程进来。那么这两个线程的锁对象就不相等。就会变成异步方法。同时可能会产生“脏读”的情况。

1
2
3
4
A1:start
A2:start
A1:end
A2:end

提示: 只要对象不变。就算对象的属性变了,运行的结果也是同步。

volatile

volatile是为了确保公共堆栈中的变量与线程私有栈中的变量保持一致。使用了volatile之后会从公共堆栈取值。
如图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public class ThreadTest2 {
public static void main(String[] args) throws InterruptedException {
volatileTest test = new volatileTest();
test.start();
Thread.sleep(1000);
test.setFlag(false);
}
}

class volatileTest extends Thread {
private boolean flag = true;
private long count = 0;

public void setFlag(boolean flag) {
this.flag = flag;
}

@Override
public void run() {
System.out.println("进入run");
while (flag) {
count++;
}
System.out.println("退出run:count = " + count);
}
}

如果线程没有及时读到最新的值。那么就可能会出现死循环。

他会一直卡在while循环中。因为在公共堆栈中已经修改了flag的值。但是在线程私有栈中却没有更新flag的值
加了volatile之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public class ThreadTest2 {
public static void main(String[] args) throws InterruptedException {
volatileTest test = new volatileTest();
test.start();
Thread.sleep(1000);
test.setFlag(false);
}
}

class volatileTest extends Thread {
private volatile boolean flag = true;
private long count = 0;

public void setFlag(boolean flag) {
this.flag = flag;
}

@Override
public void run() {
System.out.println("进入run");
while (flag) {
count++;
}
System.out.println("退出run:count = " + count);
}
}

运行结果:

1
2
进入run
退出run:count = 744998007
volatile与synchronized

乍一看volatile似乎可以解决多线程下的“脏读”问题了。但是其实上并不是。

  • volatile是线程同步的轻量级实现。所以volatile的性能比synchronized要好。但是随着JDK版本的提升。synchronized效率也的到了加强。
  • 多线程访问volatile不会发生阻塞。synchronized会发生阻塞。
  • volatile能保证数据可见性。但是不能保证原子性。而synchronized可以保证原子性。也可以间接保证可见性。因为他会将私有内存与公共内存中的数据做同步。
  • volatile解决的是变量在多个线程中的可见性。而synchronized解决的是多个线程之间访问资源的同步性。
  • 线程安全包含原子性和可见性两个方面。Java的同步机制都是围绕这两个方面来确保线程安全的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ThreadTest3 {    
public static void main(String[] args) {
for (int i = 0; i < 100; i++) { 
new volaitleTest().start();       
}
}
}
class volaitleTest extends Thread
private static int count; 
@Override   
public void run()
for (int i = 0; i < 100; i++) { 
count++;   
}


System.out.println("count:" + count);   
}
}

在所有的结果中都没有count=10000的这条结果。这个问题是因为count++并不是一个原子性操作。
解决: 有一点需要注意。也就是countstatic所修饰的。而且在main方法中是new了100个线程。所以我们在使用synchronized的时候锁对象一定是一个Class
如将synchronized添加到static方法中。或者synchronized代码块中的锁对象是一个当前类的Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class ThreadTest3 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new volaitleTest().start();
}
}
}
class volaitleTest extends Thread {
private static int count = 0;

@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (volaitleTest.class) {
count++;
}
}
System.out.println("count:" + count);
}
}

从结果中可以看出虽然顺序是随机的。但是一定有一条数据是count等于10000;
方案二 使用原子类
在前面我们使用了synchronized,这次我们不用synchronized而使用原子类。
原子类可以将i++这样的操作当成一个原子操作。而不使用锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class ThreadTest3 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new volaitleTest().start();
}
}
}

class volaitleTest extends Thread {
private static AtomicInteger count = new AtomicInteger(0);

@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (volaitleTest.class) {
count.incrementAndGet();
}
}
System.out.println("count:" + count);
}
}

可以看到,最终结果也有10000。针对与i++这样的操作可以使用原子类来做到线程安全。
还有个问题 虽然最终的结果已经是10000了,但是他不是顺序的。这是因为方法与方法之间不是同步的。将代码稍微改一下:将输出语句添加到同步块中,这样结果就是顺序的了。