java线程系列---Runnable和Thread的区别、线程同步

Runnable和Thread的区别原文

线程锁原文

鉴于我这篇文章被鄙视了,哈哈哈哈。我决定整理一下资源共享线程同步相关的知识。欢迎鄙视并谈一谈见解。


Java传统多线程的实现有两种方法,继承Thread类或者实现Runnable


在这之前需要让大家从源码上了解一下Thread和runnable这两个类,Thread 也是实现自Runnable,在runnable接口里面有个run方法。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
那其实这个 run其实就是为了多线程方面的一些扩展抽象出来的一个方法。


那我们就来看看Thread 对这个方法的实现做了什么。


 @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
内容很简短明了。大概就是执行target的run方法,这个target也有一个run方法。这个target是什么?

全局搜索看一下,在声明中可以看到。

 /* What will be run. */
    private Runnable target;

说明这个target是一个runnable对象。而它正是从Thread的构造里面传进来的。

  public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

到这里大家可能就会明白为什么多个Thread对象传入同一个Runnable对象可以实现资源共享了。

因为runnable对象是同一个啊。


我们都知道线程启动是调用了Thread对象中的start方法;

可以看一下start方法到底做了什么

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
这里面 的group是一个线程组对象ThreadGroup。start0()是一个原生方法,大概是调用jvm里面的方法了。started则是一个标志位。

说明它会把这个线程加到线程组中,这个线程组就是用来保存当前线程的一个容器,可以监测线程的生老病死,还有设置组内的优先级等,线程的执行则由start0这个原生的方法控制。


实现Runnable接口相比继承Thread类有如下好处:

1.避免单继承的局限,一个类可以同时实现多个接口

2.适合资源的共享.


接下来就写几个案例来说明资源共享和线程同步

class MyThread extends Thread {
    private int ticket = 5;

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            sale();
        }
    }

    //使用同步方法
    public void sale() {
        if (this.ticket > 0) {
            System.out.println(Thread.currentThread().getName()
                    + "卖票:1张 " + this.ticket--);
        }
    }

    public static void main(String[] args) {
//        MyThread mt = new MyThread();
        Thread thread1 = new MyThread("售票口一");
        Thread thread2 = new MyThread("售票口二");
        Thread thread3 = new MyThread("售票口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
执行结果如下:

售票口一卖票: 5
售票口一卖票: 4
售票口一卖票: 3
售票口一卖票: 2
售票口一卖票: 1
售票口三卖票: 5
售票口三卖票: 4
售票口三卖票: 3
售票口三卖票: 2
售票口三卖票: 1
售票口二卖票: 5
售票口二卖票: 4
售票口二卖票: 3
售票口二卖票: 2
售票口二卖票: 1


这边可以看出每个线程都有5张票。并没有资源共享。

class MyThread implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            sale();
        }
    }

    //使用同步方法
    public void sale() {
        if (this.ticket > 0) {
            System.out.println(Thread.currentThread().getName()
                    + "卖票: " + this.ticket--);
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread thread1 = new Thread(mt, "售票口一");
        Thread thread2 = new Thread(mt, "售票口二");
        Thread thread3 = new Thread(mt, "售票口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:


售票口二卖票: 5
售票口二卖票: 3
售票口二卖票: 2
售票口二卖票: 1
售票口三卖票: 4
售票口一卖票: 5


终于资源共享了。哎哟卧槽,怎么两个售票口都卖了一次票5,仔细思考一下。是不是有可能两个线程是同时执行ticket--的。

好像有道理。这个时候就需要同步代码块上场了。我在操作票的时候就不准你们操作了等我操作完,你们再操作。


先科普一下线程同步


JAVA多线程同步主要依赖于若干方法和关键字
  1  wait方法

  2  notify方法和notifyAll方法

  3  synchronized关键字

  4 atomic action(原子操作)

此处针对上面情况使用同步关键字synchronized解决.同步关键字使用有2种方法

  1.同步代码块

  2.同步方法


同步代码块

使用synchronized关键字进行同步代码块的声明,但是在使用此操作时必须明确的指出到底要锁定的是哪个对象,一般是以当前对象为主.

  synchronized(对象){   //一般都是讲this锁定

         //锁定对象

     }

class MyThread implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            synchronized (this) {
                sale();
            }
        }
    }

    //使用同步方法
    public void sale() {
        if (this.ticket > 0) {
            try {
                Thread.sleep(200);    //休息200毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "卖票: " + this.ticket--);
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread thread1 = new Thread(mt, "售票口一");
        Thread thread2 = new Thread(mt, "售票口二");
        Thread thread3 = new Thread(mt, "售票口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
输出如下


售票口一卖票: 5
售票口一卖票: 4
售票口一卖票: 3
售票口一卖票: 2
售票口二卖票: 1

总算正常了。不过加休眠是什么鬼,这个呃...因为不休眠线程全给一条线程跑完了。囧~~

你们可以试试休眠不加同步。这边会出很多访问的问题。


最后介绍同步方法


class MyThread implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
                sale();
        }
    }

    //使用同步方法
    public synchronized void sale() {
        if (this.ticket > 0) {
            try {
                Thread.sleep(100);    //休息200毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "卖票: " + this.ticket--);
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread thread1 = new Thread(mt, "售票口一");
        Thread thread2 = new Thread(mt, "售票口二");
        Thread thread3 = new Thread(mt, "售票口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出如下:


售票口一卖票: 5
售票口一卖票: 4
售票口一卖票: 3
售票口二卖票: 2
售票口二卖票: 1


最后因为加上了线程锁,很容易出现线程阻塞,死锁等问题。


产生死锁的必要条件和如何预防死锁


有问题欢迎探讨!!


 
 

你可能感兴趣的:(JAVA学习)