Thread的生命周期
- JDK1.5之前
- JDK1.5之后分为
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
多线程安全问题
举例,要求三个窗口同时卖票,总共有100张票,打印出卖票过程,不允许重复售卖
package Thread;
public class TestWindow1 {
public static void main(String[] args) {
SaleTik x=new SaleTik();
Thread t1 = new Thread(x);
t1.start();
Thread t2 = new Thread(x);
t2.start();
Thread t3 = new Thread(x);
t3.start();
}
}
class SaleTik implements Runnable{
int tik = 100;
@Override
public void run() {
while(true)
if(tik >=0){
System.out.println(Thread.currentThread().getName()+"窗口在卖票,剩余"+tik);
tik--;
}
}
}
运行结果
Thread-1窗口在卖票,剩余100
Thread-1窗口在卖票,剩余99
Thread-1窗口在卖票,剩余98
Thread-1窗口在卖票,剩余97
Thread-1窗口在卖票,剩余96
Thread-1窗口在卖票,剩余95
Thread-1窗口在卖票,剩余94
Thread-1窗口在卖票,剩余93
Thread-1窗口在卖票,剩余92
Thread-1窗口在卖票,剩余91
Thread-1窗口在卖票,剩余90
Thread-1窗口在卖票,剩余89
Thread-1窗口在卖票,剩余88
Thread-1窗口在卖票,剩余87
Thread-2窗口在卖票,剩余100
Thread-0窗口在卖票,剩余100
Thread-1窗口在卖票,剩余86
Thread-2窗口在卖票,剩余85
Thread-0窗口在卖票,剩余84
Thread-0窗口在卖票,剩余81
Thread-1窗口在卖票,剩余83
Thread-2窗口在卖票,剩余82
Thread-0窗口在卖票,剩余80
Thread-1窗口在卖票,剩余79
Thread-2窗口在卖票,剩余78
Thread-0窗口在卖票,剩余77
Thread-1窗口在卖票,剩余76
Thread-2窗口在卖票,剩余75
Thread-0窗口在卖票,剩余74
Thread-1窗口在卖票,剩余73
Thread-2窗口在卖票,剩余72
Thread-0窗口在卖票,剩余71
Thread-1窗口在卖票,剩余70
Thread-2窗口在卖票,剩余69
Thread-0窗口在卖票,剩余68
Thread-1窗口在卖票,剩余67
Thread-2窗口在卖票,剩余66
Thread-0窗口在卖票,剩余65
Thread-1窗口在卖票,剩余64
Thread-2窗口在卖票,剩余63
Thread-0窗口在卖票,剩余62
Thread-1窗口在卖票,剩余61
Thread-2窗口在卖票,剩余60
Thread-0窗口在卖票,剩余59
Thread-1窗口在卖票,剩余58
Thread-2窗口在卖票,剩余57
Thread-0窗口在卖票,剩余56
Thread-1窗口在卖票,剩余55
Thread-2窗口在卖票,剩余54
Thread-2窗口在卖票,剩余51
Thread-2窗口在卖票,剩余50
Thread-2窗口在卖票,剩余49
Thread-2窗口在卖票,剩余48
Thread-2窗口在卖票,剩余47
Thread-2窗口在卖票,剩余46
Thread-2窗口在卖票,剩余45
Thread-2窗口在卖票,剩余44
Thread-2窗口在卖票,剩余43
Thread-2窗口在卖票,剩余42
Thread-2窗口在卖票,剩余41
Thread-0窗口在卖票,剩余53
Thread-1窗口在卖票,剩余52
Thread-2窗口在卖票,剩余40
Thread-0窗口在卖票,剩余39
Thread-1窗口在卖票,剩余38
Thread-2窗口在卖票,剩余37
Thread-0窗口在卖票,剩余36
Thread-1窗口在卖票,剩余35
Thread-2窗口在卖票,剩余34
Thread-0窗口在卖票,剩余33
Thread-1窗口在卖票,剩余32
Thread-2窗口在卖票,剩余31
Thread-2窗口在卖票,剩余28
Thread-2窗口在卖票,剩余27
Thread-2窗口在卖票,剩余26
Thread-2窗口在卖票,剩余25
Thread-2窗口在卖票,剩余24
Thread-2窗口在卖票,剩余23
Thread-2窗口在卖票,剩余22
Thread-2窗口在卖票,剩余21
Thread-2窗口在卖票,剩余20
Thread-2窗口在卖票,剩余19
Thread-2窗口在卖票,剩余18
Thread-2窗口在卖票,剩余17
Thread-2窗口在卖票,剩余16
Thread-2窗口在卖票,剩余15
Thread-2窗口在卖票,剩余14
Thread-2窗口在卖票,剩余13
Thread-2窗口在卖票,剩余12
Thread-2窗口在卖票,剩余11
Thread-2窗口在卖票,剩余10
Thread-2窗口在卖票,剩余9
Thread-2窗口在卖票,剩余8
Thread-2窗口在卖票,剩余7
Thread-2窗口在卖票,剩余6
Thread-2窗口在卖票,剩余5
Thread-2窗口在卖票,剩余4
Thread-2窗口在卖票,剩余3
Thread-2窗口在卖票,剩余2
Thread-2窗口在卖票,剩余1
Thread-2窗口在卖票,剩余0
Thread-0窗口在卖票,剩余30
Thread-1窗口在卖票,剩余29
观察可知,Thread-N窗口在卖票,剩余100
这条信息被打印了三次,显然并不是我们期望的。
这便引入了线程的安全问题,我们希望一条线程工作时候直到完毕,再进行下一条线程的工作。
下面的“同步机制”便是为了解决这个问题。
同步机制
使用同步代码块,解决线程安全问题
方式1:同步代码块
synchronized(这里需要放独一无二的object!独一性才能保证线程安全限制成功){
//需要被同步的代码
}
说明:
需要被同步的代码,即为操作共享数据的代码
共享数据,即多个线程共同需要操作的数据,比如:ticket
需要被同步的代码,在被Synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待。
同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
同步监视器可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
package Thread;
public class TestWindow1 {
public static void main(String[] args) {
SaleTik x=new SaleTik();
Thread t1 = new Thread(x);
t1.start();
Thread t2 = new Thread(x);
t2.start();
Thread t3 = new Thread(x);
t3.start();
}
}
class SaleTik implements Runnable{
int tik = 100;
Object obj = new Object();
//因为synchronized后面一定要带一个不限类型的变量
//用于不同的Thread一起share,所以这里随意建立了一个Object类型的obj
@Override
public void run() {
while(true)
{
synchronized (obj) {
if (tik > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
System.out.println(Thread.currentThread().getName() + "售票,票号为" + tik);
tik--;
Thread.yield();//释放CPU,让其他thread有机会来一起抢票
}
else {
System.out.println("没有票了");
break;
}
}
}
}
}
运行结果
Thread-0售票,票号为100
Thread-2售票,票号为99
Thread-2售票,票号为98
Thread-2售票,票号为97
Thread-2售票,票号为96
Thread-2售票,票号为95
Thread-2售票,票号为94
Thread-2售票,票号为93
Thread-2售票,票号为92
Thread-2售票,票号为91
Thread-2售票,票号为90
Thread-2售票,票号为89
Thread-2售票,票号为88
Thread-2售票,票号为87
Thread-2售票,票号为86
Thread-2售票,票号为85
Thread-2售票,票号为84
Thread-1售票,票号为83
Thread-1售票,票号为82
Thread-1售票,票号为81
Thread-1售票,票号为80
Thread-1售票,票号为79
Thread-1售票,票号为78
Thread-1售票,票号为77
Thread-1售票,票号为76
Thread-1售票,票号为75
Thread-1售票,票号为74
Thread-1售票,票号为73
Thread-1售票,票号为72
Thread-1售票,票号为71
Thread-1售票,票号为70
Thread-1售票,票号为69
Thread-1售票,票号为68
Thread-1售票,票号为67
Thread-1售票,票号为66
Thread-1售票,票号为65
Thread-1售票,票号为64
Thread-1售票,票号为63
Thread-2售票,票号为62
Thread-2售票,票号为61
Thread-2售票,票号为60
Thread-2售票,票号为59
Thread-2售票,票号为58
Thread-2售票,票号为57
Thread-2售票,票号为56
Thread-2售票,票号为55
Thread-2售票,票号为54
Thread-2售票,票号为53
Thread-2售票,票号为52
Thread-2售票,票号为51
Thread-2售票,票号为50
Thread-2售票,票号为49
Thread-2售票,票号为48
Thread-2售票,票号为47
Thread-1售票,票号为46
Thread-1售票,票号为45
Thread-1售票,票号为44
Thread-1售票,票号为43
Thread-1售票,票号为42
Thread-1售票,票号为41
Thread-1售票,票号为40
Thread-1售票,票号为39
Thread-1售票,票号为38
Thread-0售票,票号为37
Thread-0售票,票号为36
Thread-0售票,票号为35
Thread-0售票,票号为34
Thread-0售票,票号为33
Thread-0售票,票号为32
Thread-0售票,票号为31
Thread-0售票,票号为30
Thread-0售票,票号为29
Thread-0售票,票号为28
Thread-0售票,票号为27
Thread-0售票,票号为26
Thread-0售票,票号为25
Thread-0售票,票号为24
Thread-0售票,票号为23
Thread-0售票,票号为22
Thread-0售票,票号为21
Thread-0售票,票号为20
Thread-0售票,票号为19
Thread-0售票,票号为18
Thread-0售票,票号为17
Thread-0售票,票号为16
Thread-0售票,票号为15
Thread-1售票,票号为14
Thread-1售票,票号为13
Thread-2售票,票号为12
Thread-2售票,票号为11
Thread-2售票,票号为10
Thread-2售票,票号为9
Thread-2售票,票号为8
Thread-2售票,票号为7
Thread-2售票,票号为6
Thread-2售票,票号为5
Thread-2售票,票号为4
Thread-2售票,票号为3
Thread-2售票,票号为2
Thread-2售票,票号为1
没有票了
没有票了
没有票了
Process finished with exit code 0
方式2:同步方法
说明:用extends Thread的方式完成多线程情景,三个窗口轮流售100张票。
继续构造三个窗口轮流售票的代码:
package Thread;
public class TestWindow2 {
public static void main(String[] args) {
SaleTick x = new SaleTick();SaleTick y = new SaleTick();SaleTick z = new SaleTick();
x.start();
y.start();
z.start();
}
}
class SaleTick extends Thread{
//class SaleTick implements Runnable{
static int tickets =100;
//static Object obj = new Object(); //需要确定obj是唯一的才能用于构造synchronized,可以用
@Override
public void run() {
while(true){
//synchronized (this) {//this:此时表示xyz,不能保证唯一性,不可以用
// synchronized (obj) {//obj:使用static保证唯一性 可以用
synchronized (SaleTick.class) {//(Class sth_balabla = SaleTick.class)
// 定义时候用SaleTick.class看起来是一个类,并不是个object啊?
// 其实,大写的Class的对象就是具体的某个类
//(Class 常量值 = SaleTick.class)
//类型 类型的变量名 = SaleTick.class
// 大写的Class的对象(SaleTick.class)就是具体的某个类
// 所以这里SaleTick.class并不认为是一个类,本质上是一个数值,保证唯一性,可以用来控制thread切换
if(tickets>0){
//让其他线程也有机会购票
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+tickets);
tickets--;
Thread.yield();//让其他线程机会也有机会购票
}
else break;
}
}
}
}
运行结果看到,三个窗口在轮流售票:
Thread-0售票,票号为:100
Thread-0售票,票号为:99
Thread-0售票,票号为:98
Thread-0售票,票号为:97
Thread-0售票,票号为:96
Thread-0售票,票号为:95
Thread-0售票,票号为:94
Thread-0售票,票号为:93
Thread-0售票,票号为:92
Thread-0售票,票号为:91
Thread-0售票,票号为:90
Thread-0售票,票号为:89
Thread-2售票,票号为:88
Thread-2售票,票号为:87
Thread-2售票,票号为:86
Thread-2售票,票号为:85
Thread-2售票,票号为:84
Thread-2售票,票号为:83
Thread-2售票,票号为:82
Thread-2售票,票号为:81
Thread-2售票,票号为:80
Thread-2售票,票号为:79
Thread-2售票,票号为:78
Thread-2售票,票号为:77
Thread-2售票,票号为:76
Thread-2售票,票号为:75
Thread-2售票,票号为:74
Thread-2售票,票号为:73
Thread-2售票,票号为:72
Thread-2售票,票号为:71
Thread-2售票,票号为:70
Thread-2售票,票号为:69
Thread-2售票,票号为:68
Thread-2售票,票号为:67
Thread-2售票,票号为:66
Thread-2售票,票号为:65
Thread-2售票,票号为:64
Thread-2售票,票号为:63
Thread-2售票,票号为:62
Thread-2售票,票号为:61
Thread-2售票,票号为:60
Thread-2售票,票号为:59
Thread-2售票,票号为:58
Thread-2售票,票号为:57
Thread-2售票,票号为:56
Thread-2售票,票号为:55
Thread-2售票,票号为:54
Thread-2售票,票号为:53
Thread-2售票,票号为:52
Thread-2售票,票号为:51
Thread-2售票,票号为:50
Thread-2售票,票号为:49
Thread-2售票,票号为:48
Thread-1售票,票号为:47
Thread-1售票,票号为:46
Thread-1售票,票号为:45
Thread-1售票,票号为:44
Thread-1售票,票号为:43
Thread-1售票,票号为:42
Thread-1售票,票号为:41
Thread-1售票,票号为:40
Thread-1售票,票号为:39
Thread-1售票,票号为:38
Thread-1售票,票号为:37
Thread-1售票,票号为:36
Thread-1售票,票号为:35
Thread-1售票,票号为:34
Thread-1售票,票号为:33
Thread-1售票,票号为:32
Thread-1售票,票号为:31
Thread-1售票,票号为:30
Thread-1售票,票号为:29
Thread-1售票,票号为:28
Thread-1售票,票号为:27
Thread-1售票,票号为:26
Thread-1售票,票号为:25
Thread-1售票,票号为:24
Thread-1售票,票号为:23
Thread-1售票,票号为:22
Thread-1售票,票号为:21
Thread-1售票,票号为:20
Thread-1售票,票号为:19
Thread-1售票,票号为:18
Thread-1售票,票号为:17
Thread-1售票,票号为:16
Thread-1售票,票号为:15
Thread-1售票,票号为:14
Thread-1售票,票号为:13
Thread-1售票,票号为:12
Thread-1售票,票号为:11
Thread-1售票,票号为:10
Thread-1售票,票号为:9
Thread-1售票,票号为:8
Thread-1售票,票号为:7
Thread-1售票,票号为:6
Thread-1售票,票号为:5
Thread-1售票,票号为:4
Thread-1售票,票号为:3
Thread-1售票,票号为:2
Thread-1售票,票号为:1
Process finished with exit code 0
总结:
在实现implements Runnable接口的方式中,同步监视器可以考使用this
比如写成代码synchronized (this){XXX}
在继承extends Thread类的方式中,同步监视器要慎用this,可以考虑使用当前类.class
,比如写成代码块 synchronized (SaleTick.class){XXX}
这个一般都是肯定唯一的。