Java学习33-Java 多线程Thread 多线程安全问题

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}这个一般都是肯定唯一的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值