java篇36-java中的多线程、同步代码块、同步方法、锁
一、java中实现多线程的三种方式1继承Thread类的方式进行实现2实现Runnable接口的方式进行实现3利用Callable接口和Future接口方式实现。1.继承Thread类的方式进行实现步骤自定义一个类继承Thread重写run()方法run()方法中写的是想要线程执行的代码创建子类的对象并启动线程。例如这里使用线程的getName()方法和setName()方法对线程进行区分。2.实现Runnable接口的方式进行实现自己定义一个类实现Runnable接口重写里面的run()方法创建子类的对象创建一个Thread类的对象并开启线程。例如说明上述代码中实现了Runnable接口的类是放在了新建的Thread类中的可以理解为实现了Runnable接口的类里面是线程要执行的任务将任务交给执行线程。还需要注意为了区分线程需要给线程取名setName()但是在实现了Runnable接口的类的run()方法不能直接使用getName()来获取线程名字它是Thread类中的方法解决方式是使用Thread的静态方法currentThread()来获取执行run()的当前线程然后再使用getName()方法。应用场景你可能会觉得Runnable很麻烦但要注意java中继承只有一个父类而实现接口可以多个且实现接口也可以继承所以当有继承的类想要实现多线程时就可以使用Runnable。3.利用Callable接口和Future接口方式实现可以发现前两种线程的实现方式中run()都没有返回值不能获取线程执行的结果。步骤创建一个类实现Callable接口重写call()该方法有返回值返回线程运行的结果创建实现Callable接口的类的对象表示多线程要执行的任务创建FutureTask的对象管理多线程执行的结果;创建Thread类的对象并启动。例如说明上图中Callable的泛型中指定的数据类型是重写的call()的返回值类型。说明上面定义的是FutureTask的对象通过泛型指定管理的返回值结果的类型。实现Callable接口的类可以看成指定任务将其对象作为FutureTask对象的构造器参数再将FutureTask对象给Thread对象的构造器参数最后通过Thread对象来开启线程而获取线程的执行结果是通过FutureTask对象获得。二、Thread常用的成员方法说明线程的优先级越高抢占到cpu的概率越大。1.getName()和setName()getName()和setName()在之前的代码中演示过了。需要注意的是1)如果没有用使用setName()为线程设置名字使用getName()会获取线程默认的名字格式为Thread-x(x0,1,…)。(2)除了通过setName()的方式为线程设置名字之外还可以在新建Thread对象时为线程设置名字。如果使用继承了Thread的类需要重写父类的构造方法子类不继承父类的构造方法。例如2.Thread.currentThread()前面使用过了。注意在main方法中使用Thread.currentThread().getName()后打印方法返回值可以发现值为“name”在JVM虚拟机启动后会自动启动多条线程其中一条线程叫做main线程它的作用就是调用main方法执行里面的代码。3.sleep()方法说明线程执行到这个方法会停留对应的时间由参数指定单位为毫秒1秒1000毫秒当时间到了之后线程会自动地醒来继续执行后续代码。例如下图中的代码的效果为先输出11111等待5秒后再输出000004.setPriority()和getPriority()设置和获取线程的优先级的方法。注意线程的优先级默认为5最小为1最大为10。例如注意线程的优先级并不说明一定率先占用cpu先执行完只是概率更高。5.setDaemon()与守护线程守护线程即备胎线程当其他的非守护线程执行完毕后守护线程也会陆续结束。例如对设置线程优先级的代码进行改写可以看到执行结果当“飞机”线程结束后“备胎线程”也会慢慢结束还没有输出到99就结束了。6.yield()和出让线程Thread().yield()让出cpu注意只是出让之后还是会去再抢夺。7.join()和插入线程yield()和join()用的少不过多说明。三、同步代码块同步代码块把操作共享数据的代码锁起来。格式synchronized锁{操作共享数据的代码}锁对象需要是唯一的用static关键字修饰即可。特点1锁默认打开有一个线程进去了锁自动关闭2里面的代码全部执行完毕线程出来锁自动打开。例如在不使用同步代码块的情况下一个3个窗口同时贩票的情况下可以看到贩票不按顺序且有的票重复有的没有这里涉及相同的同步机制就不过多解释。使用java中的同步代码块机制。上图中圈出的部分即为同步代码块将其置于synchroized()后的花括号中obj为锁可任意设置只要有static修饰不同线程执行该代码块时其他线程不能执行。执行结果如下补充锁一般使用当前类的字节码对象是唯一的补充一个小细节不同区域的同步代码块的锁相同也只能有一个线程执行不能分别进入不同区域。例如上例中两个继承Thread类的子类中的同步代码块用了同一把锁此时会发现只能是一个一个线程执行完run()方法里的内容后另一个线程才能执行run()里的内容。而如果无锁则两个区域的代码并发执行说明就是想说明一把锁不仅可以锁住同一个代码区还可以一起锁住多个代码区。四、同步方法同步方法是指被synchronized关键字修饰的方法。格式特点1同步方法是锁在方法里面所有的代码2锁对象不能自己指定。java已经规定好非静态的方法锁对象是自身所在类静态的同步方法的锁对象是所在类的字节码文件对象。例如说明这里要注意一下使用实现Runnable的类来为线程设置任务时ticket不用设置为静态变量因为在下图中的代码中将实现Runnable的类的一个对象分别给了不同的Thread对象。补充StringBuilder和StringBuffer的不同之处就在于StringBuilder是线程不安全的它的成员方法都没有synchronized关键字修饰而StringBuffer是线程安全的它的成员方法都由synchronized关键字修饰。五、锁jdk5之后提供了一个锁接口Lock实现了比使用synchronized方法和语句更广泛的锁定操作提供了手动获得锁和释放锁的方法。注意Lock是接口不能直接实例化要通过它的实现类ReentrantLock来进行操作。例如注意上图中的代码有一个问题就是程序一直运行没有终止原因是当有一个线程进入同步块上锁后当ticket201时票售罄此时直接执行break;语句跳出循环没有释放锁而其他两个线程还处于while()循环中等着上锁一直没有终止。解决方法是将释放锁的语句放到try-catch-finally的finally里如下