Java总结—实现Runnable接口创建线程,线程安全同步,死锁(哲学家进餐问题),读写锁

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java总结—实现Runnable接口创建线程,线程安全同步,死锁(哲学家进餐问题),读写锁相关的知识,希望对你有一定的参考价值。

一.通过实现Runnable接口创建线程

  1. 定义实现Runnable接口的类

    (1)Runnable接口中只有一个方法public void run();用来定义线程运行体:

         class MyRun implements Runnable(){

              public void run(){

                  线程执行的具体代码

              }

         }

    (2)创建线程的实例的时候将这个类的实例作为参数传递到线程实例内部。然后再启动:

        Thread thread=new Thread(new MyRun());

        Thread.start();

  2. 使用Runnable接口实现线程的优势:

    (1)优势一:避免了Java的单继承的局限性,实现Runnable接口的类还可以实现另一个类或实现其他接口

    (2)优势二:使用了实现Runnamable接口的方式 创建线程时可以为相同的程序代码的多个线程提供共享数据(资源共享)

    1(实现Runnable接口创建线程)

    实现Runnable接口创建线程:

    package runnable;

    /**

     * 通过实现Runnable接口创建线程

     * @author Administrator

     */

    public class MyRunnable implements Runnable{

     

    @Override

    public void run() {

    for (int i = 1; i <=40; i++) {

    if (i%2==0) {

    try {

    Thread.sleep(500);//线程休眠500ms

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"获取数据i="+i);

    }

    }

    }

     

    }

    测试线程:

    package runnable;

     

    public class TestRunable {

     

    public static void main(String[] args) {

    MyRunnable mr=new MyRunnable();//实例化子线程对象

    new Thread(mr,"求偶线程——>").start();//启动子线程

    }

     

    }

    运行结果:

    求偶线程——>获取数据i=2

    求偶线程——>获取数据i=4

    求偶线程——>获取数据i=6

    求偶线程——>获取数据i=8

    求偶线程——>获取数据i=10

    求偶线程——>获取数据i=12

    求偶线程——>获取数据i=14

    求偶线程——>获取数据i=16

    求偶线程——>获取数据i=18

    求偶线程——>获取数据i=20

    求偶线程——>获取数据i=22

    求偶线程——>获取数据i=24

    求偶线程——>获取数据i=26

    求偶线程——>获取数据i=28

    求偶线程——>获取数据i=30

    求偶线程——>获取数据i=32

    求偶线程——>获取数据i=34

    求偶线程——>获取数据i=36

    求偶线程——>获取数据i=38

    求偶线程——>获取数据i=40

    *此例中并未实现资源共享,学习同步之后再举例资源共享

    二.多线程的安全与同步问题

    1.当run()方法体内的代码操作到了成员变量(共享资源)时,就可能会出现多线程的安全问题(线程不同步问题)

    2.编程技巧:在方法中尽量少操作成员变量(在不需要共享资源时),多使用局部变量

    3.线程的同步

    (1)再Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性,每一个对象都对应于可称为“互斥锁”的标记,这个标记保证在任何时候,只能有一个线程访问对象

    (2)关键字synchronized用来与对象的互斥锁相关联,当某个对象用synchronized修饰时,表明该对象在任何时候只能由一个线程访问(这个对象就变成了同步对象)

    (3)一个方法使用关键字synchronizd修饰后,当一个线程A使用这个方法时,其他线程想使用这个方法就必须等待,直到线程A使用完这个方法(前提这些线程使用的是同一个同步对象)

    4.同步方法的同步对象

    1)对于静态方法来说,this当前对象充当了同步对象

    2)对于静态方法来说,同步对象是类对象“类对象”代表的是这个类的本身,所有通过实例化的普通对象共享这个“类对象”

    三.使用synchronized关键字实现同步(卖票问题)

    1.synchronized的使用方法:

    1)同步代码块:synchronized放在对象前面限制一段代码的执行

    Synchronized(同步代码块){

    需要同步的代码

    }

    2)同步方法:synchronized放在方法声明中,表明同步方法

    public synchronized void method(){

    .........

    }

    2.加上同步机制后,效率低的原因:

    1)会丧失Java多线程的并发优势,在执行到同步代码块(或同步方法)时,只能有一个线程执行,其他线程必须等待执行同步代码块(同步方法)的线程释放同步对象的锁

    2)其他等待锁释放的线程会不断检查锁的状态

    1(同步代码块):

    包含同步代码块的线程:

    package synchronizeddemo;

     

    public class SynchronizedRun implements Runnable{

    private int tickets=5;

     

    @Override

    public void run() {

    for (int i = 1; i <=100; i++) {//故意是循环次数大于总票数

    synchronized (this) {//同步代码块

    if(tickets>0){//如果还有票则出售

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");

    }

    }

    }

    }

     

    }

    测试同步代码块:

    package synchronizeddemo;

     

    public class TestSyn {

     

    public static void main(String[] args) {

    new Thread(new SynchronizedRun(),"窗口一——>").start();

    new Thread(new SynchronizedRun(),"窗口二——>").start();

    new Thread(new SynchronizedRun(),"窗口三——>").start();

     

    }

     

    }

    运行结果:

    窗口一——>正在出售第5张票

    窗口三——>正在出售第5张票

    窗口二——>正在出售第5张票

    窗口三——>正在出售第4张票

    窗口二——>正在出售第4张票

    窗口一——>正在出售第4张票

    窗口三——>正在出售第3张票

    窗口二——>正在出售第3张票

    窗口一——>正在出售第3张票

    窗口三——>正在出售第2张票

    窗口二——>正在出售第2张票

    窗口一——>正在出售第2张票

    窗口三——>正在出售第1张票

    窗口一——>正在出售第1张票

    窗口二——>正在出售第1张票

    **由结果可见,并未实现资源共享,因为在循环的过程中,每次都创建性新线程拿到的都是新锁,与前面的锁不同

    **测试类稍作改进后:

    package synchronizeddemo;

     

    public class TestSyn {

     

    public static void main(String[] args) {

    SynchronizedRun sr=new SynchronizedRun();

    new Thread(sr,"窗口一——>").start();

    new Thread(sr,"窗口二——>").start();

    new Thread(sr,"窗口三——>").start();

     

    }

     

    }

    运行结果:

    窗口一——>正在出售第5张票

    窗口一——>正在出售第4张票

    窗口三——>正在出售第3张票

    窗口三——>正在出售第2张票

    窗口三——>正在出售第1张票

    2(同步方法):

    创建同步方法线程:

    package synchronizeddemo;

     

    public class SynchronizedRun implements Runnable{

    private int tickets=5;

     

    @Override

    public void run() {

    for (int i = 0; i <100; i++) {

    sell();

    }

     

    }

     

    public synchronized void sell() {//同步方法,同步方法中this充当同步 对象

    if(tickets>0){//如果还有票,就卖票

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");

    }

     

    }

     

    }

    测试同步方法线程

    package synchronizeddemo;

     

    public class TestSyn {

     

    public static void main(String[] args) {

    SynchronizedRun sr=new SynchronizedRun();

    new Thread(sr,"窗口一——>").start();

    new Thread(sr,"窗口二——>").start();

    new Thread(sr,"窗口三——>").start();

     

    }

     

    }

    运行结果:

    窗口一——>正在出售第5张票

    窗口一——>正在出售第4张票

    窗口一——>正在出售第3张票

    窗口三——>正在出售第2张票

    窗口三——>正在出售第1张票

    3(静态同步方法)

    静态同步线程:

    package staticsyn;

     

    public class MakeMoney implements Runnable{

    private int money=5000;

     

    @Override

    public void run() {

    makeMoney();

    }

     

    //静态方法的同步对象是“类对象”,类对象代表这个类本身,所有通过实例化的普通对象共享这个"类对象"

    private static synchronized void makeMoney() {

    String th_name=Thread.currentThread().getName();

    System.out.println(th_name+"开始帮你赚钱...");

    try {

    Thread.sleep(1000);//当期线程休眠1000ms

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(th_name+"帮你赚钱完毕...");

    }

     

    }

    测试静态同步线程:

    package staticsyn;

     

    public class TestMakeMoney {

     

    public static void main(String[] args) {

    new Thread(new MakeMoney(),"工人一——>").start();

    new Thread(new MakeMoney(),"工人二——>").start();

    new Thread(new MakeMoney(),"工人三——>").start();

    }

     

    }

    运行结果:

    工人一——>开始帮你赚钱...

    工人一——>帮你赚钱完毕...

    工人三——>开始帮你赚钱...

    工人三——>帮你赚钱完毕...

    工人二——>开始帮你赚钱...

    工人二——>帮你赚钱完毕...

     

    四.死锁问题

    1.死锁的原因:

    1)线程一锁住资源A等待资源B,线程二锁住资源B等待资源A,两个线程都在等待自己所需要的资源,而这些资源被另外的线程锁住,这些线程你等我,我等你,谁也不愿意让出资源,这样死锁就产生了

    2.哲学家进餐问题:

    例(以哲学家进餐问题为例):

    叉子类:

    package deadlock;

    /**

     * 叉子类

     * @author Administrator

     */

    public class Fork {

    public void forkSay() {

    System.out.println("我拿到叉子了,请给我刀子...");

    }

     

    }

    刀子类:

    package deadlock;

    /**

     * 刀子类

     * @author Administrator

     */

    public class Knife {

    public void knifeSay(){

    System.out.println("我拿到刀子了,请给我擦子....");

    }

     

    }

    哲学家进餐类:

    package deadlock;

     

    /**

     * 哲学家进餐,相互拿着对方需要的资源,不愿意放弃

     * @author Qi

     */

    public class PhilosopherRunnable implements Runnable {

    private boolean flag = false;

    private static  Knife knife = new Knife();// 刀子资源

    private  static Fork fork = new Fork();// 叉子资源

     

    public void setFlag(boolean flag) {

    this.flag = flag;

    }

     

    @Override

    public void run() {

    if(flag){

    synchronized (knife) {

    knife.knifeSay();

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    synchronized (fork) {

    fork.forkSay();

    }

    }

    System.out.println(Thread.currentThread().getName()+"进完餐了....");

    }else{

    synchronized (fork) {

    fork.forkSay();

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    synchronized (knife) {

    knife.knifeSay();

    }

    }

    System.out.println(Thread.currentThread().getName()+"进完餐了....");

    }

     

    }

     

    }

    测试哲学家进餐:

    package deadlock;

     

    public class TestPhil {

     

    public static void main(String[] args) {

    PhilosopherRunnable pn=new PhilosopherRunnable();

    pn.setFlag(true);

    PhilosopherRunnable pn1=new PhilosopherRunnable();

    new Thread(pn,"苏格拉底").start();

    new Thread(pn1,"柏拉图").start();

     

    }

     

    }

    运行结果:

    我拿到叉子了,请给我刀子...

    我拿到刀子了,请给我擦子....

     

     技术分享图片

     

     

     

    由图可见产生了死锁

     

    五.Lock实现同步

    *java.util.concurrent.locks包下的相关接口与类

    1.Lock接口:通常使用Lock接口中的方法用来获取锁,其中lock()方法是使用最多的一个方法。

    2.ReentrantLock(”可重入锁“):ReentrantLock接口的类

    3.ReadWriteLock接口(包括了两个方法):

    (1)Lock readLock();用来获取”读锁“

    (2)Lock writeLock();用来获取”写锁“

    4.ReentrantReadWriteLock类:是实现ReadWriteLock接口的实现类

    5.关于线程的读锁和写锁

    (1)如果有一个线程已经占用了读锁,则此时如果其他线程要申请写锁,则申请写锁的线程会一直 等待释放读锁,但其他线程申请读锁是可以的。

    (2)如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或读锁,则申请的线程会一直等待释放写锁

    6.SynchronizedLock

    (1)Lock是一个接口,而synchronizedJava中的关键字,synchronized是内置的语言实现

    (2)Synchronized在发异常时,会自动释放 线程所占用的锁,而Lock在发生异常时,如果没有unLock()去释放锁,则可能造成死锁现象,因此使用Lock时需要在finally块中释放锁

    (3)Lock可以提高多个线程进行读操作的效率

    1(获取锁与释放锁):

    卖票线程(包含获取锁与释放锁):

    package lock;

     

    import java.util.concurrent.locks.Lock;

    import java.util.concurrent.locks.ReentrantLock;

     

    public class TicketLock implements Runnable{

    private int tickets=10;

    private Lock lock=new ReentrantLock();

     

    @Override

    public void run() {

    for (int i = 0; i < 100; i++) {

    lock.lock();//获取锁

    try {

    if (tickets>0) {

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");

    }

    } catch (Exception e) {

    System.out.println("捕获到的异常为:"+e.getMessage());

    }finally{

    lock.unlock();//释放锁

    }

     

    }

     

    }

     

    }

    测试释放锁与获取锁线程:

    package lock;

     

    public class TestLock {

     

    public static void main(String[] args) {

    TicketLock tk=new TicketLock();

    new Thread(tk,"窗口一——>").start();

    new Thread(tk,"窗口二——>").start();

    new Thread(tk,"窗口三——>").start();

    }

     

    }

    运行结果:

    窗口一——>正在出售第10张票

    窗口一——>正在出售第9张票

    窗口一——>正在出售第8张票

    窗口一——>正在出售第7张票

    窗口一——>正在出售第6张票

    窗口一——>正在出售第5张票

    窗口一——>正在出售第4张票

    窗口一——>正在出售第3张票

    窗口一——>正在出售第2张票

    窗口一——>正在出售第1张票

     

     2(读锁与写锁):

    获取读锁写锁线程:

    package rwlock;

     

    import java.util.Random;

    import java.util.concurrent.locks.ReadWriteLock;

    import java.util.concurrent.locks.ReentrantReadWriteLock;

     

    public class RWLock implements Runnable{

    private static int date;

    private boolean flag=false;

    private ReadWriteLock rwl=new ReentrantReadWriteLock();

     

    public void setFlag(boolean flag) {

    this.flag = flag;

    }

     

    @Override

    public void run() {

    if (flag) {

    for (int i = 0; i < 20; i++) {

    writeLock();

    }

    }else{

    for (int j = 0; j < 20; j++) {

    readLock();

    }

    }

     

    }

     

    public void writeLock(){

    try {

    rwl.writeLock().lock();//获取写锁

    System.out.println(Thread.currentThread().getName()+"开始写入数据....");

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    date=new Random().nextInt(10);//产生随机数

    System.out.println(Thread.currentThread().getName()+"写入数据完毕....");

    }finally{

    rwl.writeLock().unlock();//释放写锁

    }

    }

     

    public void readLock(){

    try {

    rwl.readLock().lock();//获取读锁

    System.out.println(Thread.currentThread().getName()+"开始读取数据.....");

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"读取到的数据为:"+date);

    }finally{

    rwl.readLock().unlock();//释放读锁

    }

    }

     

    }

    测试读锁写锁线程:

    package rwlock;

     

    public class TestRWL {

     

    public static void main(String[] args) {

    RWLock rw=new RWLock();

    rw.setFlag(true);

    new Thread(rw,"写入线程一——>").start();

    new Thread(rw,"写入线程二——>").start();

     

    new Thread(new RWLock(),"读取线程一——>").start();

    new Thread(new RWLock(),"读取线程二——>").start();

    new Thread(new RWLock(),"读取线程三——>").start();

     

    }

     

    }

    运行结果为:

    写入线程二——>开始写入数据....

    读取线程二——>开始读取数据.....

    读取线程三——>开始读取数据.....

    读取线程一——>开始读取数据.....

    读取线程二——>读取到的数据为:0

    读取线程一——>读取到的数据为:0

    读取线程一——>读取到的数据为:3

    读取线程一——>开始读取数据.....

    读取线程三——>读取到的数据为:4

    读取线程三——>开始读取数据.....

    写入线程二——>写入数据完毕....

    写入线程二——>开始写入数据....

    读取线程二——>读取到的数据为:4

    读取线程二——>开始读取数据.....

    读取线程二——>读取到的数据为:4

    读取线程二——>开始读取数据.....

    写入线程二——>写入数据完毕....

    写入线程二——>开始写入数据....

    读取线程三——>读取到的数据为:3

    读取线程三——>开始读取数据.....

    读取线程一——>读取到的数据为:3

    读取线程一——>开始读取数据.....

    读取线程三——>读取到的数据为:3

    读取线程一——>读取到的数据为:3

    读取线程一——>开始读取数据.....

    写入线程二——>写入数据完毕....

    写入线程二——>开始写入数据....

    读取线程三——>开始读取数据.....

    读取线程二——>读取到的数据为:3

    读取线程二——>开始读取数据.....

    写入线程二——>写入数据完毕....

    读取线程二——>读取到的数据为:2

    读取线程一——>读取到的数据为:2

    读取线程三——>读取到的数据为:2

    读取线程一——>开始读取数据.....

    读取线程二——>开始读取数据.....

    写入线程二——>开始写入数据....

    读取线程三——>开始读取数据.....

    读取线程三——>读取到的数据为:2

    写入线程二——>写入数据完毕....

    读取线程二——>读取到的数据为:2

    读取线程一——>读取到的数据为:2

    读取线程二——>开始读取数据.....

    写入线程二——>开始写入数据....

    读取线程三——>开始读取数据.....

    读取线程一——>开始读取数据.....

    读取线程三——>读取到的数据为:2

    读取线程一——>读取到的数据为:3

    读取线程二——>读取到的数据为:3

    写入线程二——>写入数据完毕....

    读取线程二——>开始读取数据.....

    读取线程一——>开始读取数据.....

    读取线程三——>开始读取数据.....

    读取线程一——>开始读取数据.....

    读取线程三——>读取到的数据为:4

    ............................

    ............................

     

    【本次总接完毕】(线程二模块)

    2018.2.16

 

以上是关于Java总结—实现Runnable接口创建线程,线程安全同步,死锁(哲学家进餐问题),读写锁的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程:实现Runnable接口创建线程方式详解

java线程

Java多线程总结

Java之多线程方式二(实现Runnable接口)

JAVA多线程用实现Runnable接口的方式创建线程

Java创建线程的两种方法