文章摘要 |
---|
对比继承和实现方法,线程安全与线程同步及其方法 |
对比继承Thread和实现Runable两种方法
继承Thread
1.java中类是单继承的,如果继承Thread,该类就不能有其他父类了
2.从操作上看,继承方式更简单
3.但无法做到多线程共享同一资源
实现Runable
1.java可实现多接口,该类还可继承其它父类
2.从操作上看,实现方法更复杂,获取线程名称也更复杂(Thread.currentThread())
3.能做到共享同一资源
解释一下共享同一资源:
假设我有50个苹果,有A,B,C三个人同时吃苹果
继承:
public class Xianc {
public static void main(String[] args) {
new eatapple("A").start();
new eatapple("B").start();
new eatapple("C").start();
}
}
class eatapple extends Thread{
private int num = 50;
public eatapple(String name) {
super(name);
}
public void run() {
for(int i = 1;i<=50;i++) {
if(num>0) {
System.out.println(super.getName()+"吃了"+num--+"个苹果");//获取名称这与实现的不同
}
}
}
}
实现
public class Xianc {
public static void main(String[] args) {
eatapple e = new eatapple();
new Thread(e,"A").start();
new Thread(e,"B").start();
new Thread(e,"C").start();
}
}
class eatapple implements Runnable{
private int num = 50;
public void run() {
for(int i = 1;i<=50;i++) {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果");
}
}
}
}
运行后的继承方法控制台输出的是ABC各吃了50个苹果。
而使用实现接口方法,控制台输出的是ABC一共吃了50个苹果
线程同步
线程不安全
当多线程并发的访问同一个资源对象时,可能出现线程不安全的情况
举例:
public class Xianc {
public static void main(String[] args) {
eatapple e = new eatapple();
new Thread(e,"A").start();
new Thread(e,"B").start();
new Thread(e,"C").start();
}
}
class eatapple implements Runnable{
private int num = 50;
public void run() {
for(int i = 1;i<=50;i++) {
if(num>0) {
try {
Thread.sleep(10); //使线程不安全的现象更加明显,并不是sleep()方法导致的线程不安全
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果");
}
}
}
}
运行后发现:(截取有代表性的一部分)
发现B和C同时吃了49号和48号的苹果。
原因:上述代码每个线程的操作分为两部分:
1.展示手上拿到的苹果编号
2.再吃掉苹果(苹果数减一)
而这两部分未能同步进行。
解决方法:
1.同步代码块:
2.同步方法:
3.锁机制:
同步代码块
格式:
syncronized(同步锁)
{
需要同步的代码
}
代码如下:
public void run() {
for(int i = 1;i<=50;i++) {
synchronized (this) {
if(num>0) {
try {
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果");
}
}
}
}
同步方法
(使用synchronized修饰的方法叫同步方法,保证A线程执行该方法时,其他线程只能在外边等着)
格式:synchronized public void doWork(){ //TODO }
代码如下
public void run() { //不能用synchronized修饰run方法
for(int i = 1;i<=50;i++) {
eat();
}
}
//定义一个用用synchronized修饰的新方法,并由run方法调用
synchronized private void eat() {
if(num>0) {
try {
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果");
}
}
}
同步锁:
对于非static方法,同步锁是this;
对于static方法,同步锁是当前方法所在的字节码对象(Apple2.class)
使用synchronized的利弊:
好处:保证了多线程并发访问的同步操作,避免了线程的安全性问题
缺点:用synchronized修饰的代码块性能低
建议:尽量减少synchronized的作用域
锁机制
Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,上述两种方法具有的功能Lock都有,并更加强大,更能体现面向对象思想
格式:
class X{
private final ReentrantLock lock = new ReentrantLock();
//......
public void m(){ //m是需要同步的方法
lock.lock(); //实例(第一个lock)调用lock()方法
try{
}catch(){
}finally{
lock.unlock(); //必须要有的步骤,关闭锁!
}
}
}
代码如下
public class Xianc {
public static void main(String[] args) {
eatapple e = new eatapple();
new Thread(e,"小A").start();
new Thread(e,"小B").start();
new Thread(e,"小C").start(); //通过实例化eatapple创建三个线程
}
}
class eatapple implements Runnable{
private int num = 50; //苹果数量
private final Lock l = new ReentrantLock(); //创建一个锁对象
public void run() {
for(int i = 1;i<=50;i++) {
eat();
}
}
private void eat() {
l.lock();
try {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"吃了"+num+"个苹果");
Thread.sleep(10);
num--;
}
}catch(InterruptedException e){
e.printStackTrace();
}finally {
l.unlock();
}
}
}