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.Synchronized与Lock

    (1)Lock是一个接口,而synchronized是Java中的关键字,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

原文地址:http://blog.51cto.com/13501268/2071911

时间: 2024-12-15 14:41:22

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

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

先看例子: /**实现Runnable接口创建线程步骤: * 1.创建一个实现Runnable接口的类 * 2.重写Runnable类中抽象的run()方法 * 3.创建实现类的对象 * 4.声明Thread类,同时将实现类对象作为参数传递 * 5.用Thread类的对象调用start() */ //例子:多线程售票(暂未安全同步) class MyThread implements Runnable{ private int num = 100; public void run(){ whil

实现Runnable接口创建多线程及其优势

实现Runnable接口创建多线程: 创建一个Runnable接口的实现类RunnableImpl: 主线程中: 其中,链式编程的Thread类的静态方法currentThread方法点getName是获取的是当前线程的名称: 运行结果: 线程抢占cpu资源是随机的,无法人为控制: 实现Runnable接口创建多线程的优势: 避免单继承,可以实现其他接口: 降低耦合性,增强程序扩展性: 如在创建一个Runnable接口的实现类RunnableImpl2: 即创建一个新的run方法创建了一个新的线

java核心学习(二十一) 多线程---创建启动线程的三种方式

本节开始java多线程编程的学习,对于操作系统.进程.线程的基本概念不再赘述,只是了解java对于多线程编程的支持有哪些. 一.继承Thread类来创建线程 java语言中使用Thread类来代表线程,代表线程的类可以通过继承Thread类并重写run()方法来实现多线程开发,调用线程类实例的start方法来启动该线程.下面来试一试 package ThreadTest; public class FirstThread extends Thread{ private int i; public

第8章 用户模式下的线程同步(3)_Slim读写锁(SRWLock)

8.5 Slim读/写锁(SRWLock)——轻量级的读写锁 (1)SRWLock锁的目的 ①允许读者线程同一时刻访问共享资源(因为不存在破坏数据的风险) ②写者线程应独占资源的访问权,任何其他线程(含写入的线程)要等这个写者线程访问完才能获得资源. (2)SRWlock锁的使用方法 ①初始化SRWLOCK结构体 InitializeSRWLock(PSRWLOCK pSRWLock); ②写者线程调用AcquireSRWLockExclusive(pSRWLock);以排它方式访问   读者线

java 第55节 实现Runnable接口创建线程

2016-07-01 package com.java1995; public class RunnableDemo { public static void main(String[] args) { MyThread mt=new MyThread(); Thread t=new Thread(mt); t.start(); for(int i=0;i<10;i++){ try { Thread.sleep(100); } catch (InterruptedException e) { /

实现接口创建线程

一.理论 1.进程与线程 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程. 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2. 进程 几乎所有操作系统都支持进程的概念,所有运行中的任务通常对应一条进程(Process).当一个程序进入内存运行,即变成一个进程.进程是处于运行过程中的程序,并且具有一定独立功能, 进程是系统进行资源分配和调度的一个独立单位. 一般而言,进程包含如下三个特征: <1>独立性:进程是系统

Java知多少(58)线程Runnable接口和Thread类详解

大多数情况,通过实例化一个Thread对象来创建一个线程.Java定义了两种方式: 实现Runnable 接口: 可以继承Thread类. 下面的两小节依次介绍了每一种方式. 实现Runnable接口 创建线程的最简单的方法就是创建一个实现Runnable 接口的类.Runnable抽象了一个执行代码单元.你可以通过实现Runnable接口的方法创建每一个对象的线程.为实现Runnable 接口,一个类仅需实现一个run()的简单方法,该方法声明如下:    public void run( )

创建线程的第二种方式------实现Runnable接口的方式

package cn.itcast.demo16.Demo07.Runnable; /** * @author newcityman * @date 2019/7/22 - 23:17 */public class RunnableImpl implements Runnable { @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getN

java基础——实现Callable接口创建线程

package callable; /* 创建线程方式三:实现Callable接口 1.创建一个实现Callable的实现类 2.实现call方法,将此线程需要执行的操作声明在次方法中 3.创建Callable接口实现类的对象 4.将此对象作为参数丢到FutureTask构造器中,创建FutureTask对象 5.将FutureTask对象作为对象传递到Thread构造器中,创建Thread对象,start() 6.如果需要方法的返回值,则用futureTask.get()方法去获取 理解: 1