前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 多线程编程(上)

Java 多线程编程(上)

作者头像
润森
发布2019-11-04 17:46:19
4430
发布2019-11-04 17:46:19
举报
文章被收录于专栏:毛利学Python

学习一时爽,一直学习一直爽

  Hello,大家好,我是 もうり,一个从无到有的技术+语言小白。

https://blog.csdn.net/weixin_44510615/article/details/102617286

Java多线程往往决定Java水平

在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

实现 Runnable 接口

  • main 入口函数创建线程
  • 线程执行的 run 方法,所以继承 Runnable 接口,改写 run 方法

RunnabeDemo.java

代码语言:javascript
复制
/** * @author: 毛利 */class RunnableDemo implements Runnable {    private Thread t;    private String threadName;    RunnableDemo( String name) {        threadName = name;        System.out.println("Creating " +  threadName );    }    public void run() {        System.out.println("Running " +  threadName );        try {            for(int i = 4; i > 0; i--) {                System.out.println("Thread: " + threadName + ", " + i);                // 让线程睡眠一会0.05                Thread.sleep(50);            }        }catch (InterruptedException e) {            System.out.println("Thread " +  threadName + " interrupted.");        }        System.out.println("Thread " +  threadName + " exiting.");    }    public void start () {        System.out.println("Starting " +  threadName );        if (t == null) {            t = new Thread (this, threadName);            t.start ();        }    }}

TestThread.java

代码语言:javascript
复制
/** * @author: 毛利 */public class TestThread {    public static void main(String args[]) {        RunnableDemo R1 = new RunnableDemo( "Thread-1");        R1.start();        RunnableDemo R2 = new RunnableDemo( "Thread-2");        R2.start();    }}
通过继承 Thread 类本身

单线程

代码语言:javascript
复制
/** * @author: 毛利 */public class demo1 extends Thread {    // 覆盖 run 方法    @Override    public void run() {        for (int i = 0; i < 5; i++) {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            System.out.println(this.getName() + ",i=" + i);        }    }    public static void main(String[] args) {        // 程序开始,执行的线程名字叫做main        System.out.println("程序开始,执行的线程名字叫做" + Thread.currentThread().getName());        // 创建demo1对象        demo1 demo1 = new demo1();        // 执行run方法        demo1.start();        /*        Thread-0,i=0        Thread-0,i=1        Thread-0,i=2        Thread-0,i=3        Thread-0,i=4         */    }}

如果我再添加一共 demo2 线程

代码语言:javascript
复制
demo1 demo2 = new demo1();demo2.start();

发现了

竟然是同步运行的

是时候吹吹 synchronized修饰符,难度使用 synchronized修饰符就一定同步了?

现在使用继承 Runnable 方法来看看毛利是不是穷人

代码语言:javascript
复制
/** * @author: 毛利 */public class demo2 implements Runnable{    /*    创建生产者和消费者的概念     */    // 一开始毛利没有钱的    private int count = 0;    // 毛利我挣钱    public synchronized void addMoney(){        double money = Math.floor(Math.random()*100);        count += money;        System.out.println( "毛利我挣了"+ money + "元");    }    // 毛利我花钱    public synchronized void subMoney(){        double money =  Math.floor(Math.random()*100);        if (count - money < 0 ){            count -= money;            System.out.println("毛利你又花了"+ money + "你现在负债啊啊,准备喝西北风了");        }        else {            count -= money;            System.out.println("毛利又花了" + money + ",准备吃土了");        }    }    // 看看毛利口袋    public void lookMoney(){        System.out.println("毛利现在口袋只有"+count);    }    public static void main(String[] args) {        demo2 maoli = new demo2();        maoli.run();    }    @Override    public void run() {        for (int j =0 ; j<10;j++){            addMoney();            subMoney();            lookMoney();        }    }}

运行结果如下,果然我还是一个穷人,天天喝西北风

代码语言:javascript
复制
毛利我挣了79.0元毛利你又花了95.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-16毛利我挣了2.0元毛利你又花了35.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-49毛利我挣了2.0元毛利你又花了14.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-61毛利我挣了34.0元毛利你又花了26.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-53毛利我挣了36.0元毛利你又花了63.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-80毛利我挣了57.0元毛利你又花了7.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-30毛利我挣了67.0元毛利你又花了66.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-29毛利我挣了68.0元毛利你又花了63.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-24毛利我挣了76.0元毛利你又花了96.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-44毛利我挣了38.0元毛利你又花了80.0你现在负债啊啊,准备喝西北风了毛利现在口袋只有-86

这次程序是一个一个运行的。不是一段一段的

这次毛利创建两个线程看看毛利是否吃土

代码语言:javascript
复制
public static void main(String[] args) throws InterruptedException {        demo2 maoli = new demo2();        // maoli.run();        Thread thread1 = new Thread(maoli);        Thread thread2 = new Thread(maoli);        //start() 本身就是执行run        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println("终于知道毛利是一个穷光蛋了");    }

两个线程同步运行

thread1.start();thread2.start(); 意思是开启了 thread1 和 thread2,也就是两个子线程都开始执行 run 方法。

最后毛利果然还是喝西北风

thread1.join();thread2.join(); 意思是 thread1 子线程执行完再执行主线程,thread2 子线程执行完再执行主线程. 最后输出毛利是穷光蛋

毛利我要专业点说说 synchronized

  • synchronized

如果一个对象对多个线程可见,则对该对象变量的所有读取和写入都是通过同步方法完成的。

通俗:能够保证你在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

synchronized 是 Java 的关键字,是最基本的互斥同步手段,是并发编程必学内容。

并发后果

Java 并发一道好题

代码语言:javascript
复制
/** * @author: 毛利 */public class demo3 implements Runnable {    // main 方法静态,所以num必须是加static    static int num = 0;    @Override    public void run() {        for (int i = 0 ;i<10000;i++){            num++;        }    }    public static void main(String[] args) throws InterruptedException {        demo3 main = new demo3();        Thread thread1 = new Thread(main);        Thread thread2 = new Thread(main);        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println(num);    }}

代码如下创建两个线程跑 run 方法,最后我想知道这个 num 到底是多少?

2 个线程,10000*2=20000

NO? 纳里?

如果两个线程存在一种现象不是同步所有读取和写入,那么就不可能是 20000

答案是 0-20000 之间的任何一个数

加上 synchronized 问题就解决了

代码语言:javascript
复制
@Overridepublic synchronized void run() {    for (int i = 0 ;i<10000;i++){        num++;    }}

当然还可以不用在函数入口加, 添加 synchronized(this)一样的

代码语言:javascript
复制
@Overridepublic  void run() {    synchronized(this) {        for (int i = 0; i < 10000; i++) {            num++;        }    }}

当两个并发线程访问同一个对象 object 中的这个 synchronized(this) 同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

上面的代码修饰的 synchronized 是非静态方法,如果修饰的是静态方法(static)含义是完全不一样的。

但是如果我 new 两个对象,这样就又 2 个 object 的对象锁,答案又变了

代码语言:javascript
复制
/** * @author: 毛利 */public class demo3 implements Runnable {    // main 方法静态,所以num必须是加static    static int num = 0;    @Override    public  void run() {        synchronized(this) {            for (int i = 0; i < 10000; i++) {                num++;            }        }    }    public static void main(String[] args) throws InterruptedException {        // new两个对象        Thread thread1 = new Thread(new demo3());        Thread thread2 = new Thread(new demo3());        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println(num);        // 0-20000    }}

是时候抛出对象锁,不过先了解下类锁

  • 类锁:在代码中的方法上加了 static 和 synchronized 的锁,或者 synchronized(xxx.class)

类锁对应的关键字是 static sychronized,是一个全局锁,无论多少个对象否共享同一个锁(也可以锁定在该类的 class 上或者是 classloader 对象上),同样是保障同一个时刻多个线程同时访问同一个 synchronized 块,当一个线程在访问时,其他的线程等待。

  • 私有锁:在类内部声明一个私有属性如 private Object lock,在需要加锁的代码段 synchronized(lock)
  • 对象锁:在代码中的方法上加了 synchronized 的锁,或者 synchronized(this) 的代码段 方法锁和私有锁:都属于对象锁

对象锁

对象锁对应 synchronized 关键字,当多个线程访问多个实例时,它们互不干扰,每个对象都拥有自己的锁,如果是单例模式下,那么就是变成和类锁一样的功能。

对象锁防止在同一个时刻多个线程访问同一个对象的 synchronized 块。如果不是同一个对象就没有这样子的限制。

Synchronized(对象锁)和 Static Synchronized(类锁)的区别

代码语言:javascript
复制
/** * @author: 毛利 */public class demo4 {    //  Synchronized(对象锁)    public synchronized void test1() {        int i = 5;        while (i-- > 0) {            System.out.println(Thread.currentThread().getName() + " : " + i);            try {                Thread.sleep(500);            } catch (InterruptedException e) {            }        }    }    // Static Synchronized(类锁)    public static synchronized void test2() {        int i = 5;        while (i-- > 0) {            System.out.println(Thread.currentThread().getName() + " : " + i);            try {                Thread.sleep(500);            } catch (InterruptedException e) {            }        }    }    public static void main(String[] args) {        demo4 demo4 = new demo4();        // 采用Runable 复写 run        Thread test1 = new Thread(new Runnable() {            public void run() {                demo4.test1();            }        }, "test1");        Thread test2 = new Thread(new Runnable() {            public void run() {                demo4.test2();            }        }, "test2");        test1.start();        test2.start();    }    /*    test2 : 4    test1 : 4    test2 : 3    test1 : 3    test2 : 2    test1 : 2    test1 : 1    test2 : 1    test2 : 0    test1 : 0     */}
  • Static Synchronized(类锁)比 Synchronized(对象锁)访问快

下面使用同一个对象的多个实例

代码语言:javascript
复制
public static void main(String[] args) {        demo4 demo4 = new demo4();        // 采用Runable 复写 run        Thread test1 = new Thread(new Runnable() {            public void run() {                demo4.test1();            }        }, "test1");        Thread test2 = new Thread(new Runnable() {            public void run() {                demo4.test1();            }        }, "test2");        Thread test3 = new Thread(new Runnable() {            public void run() {                demo4.test2();            }        }, "test3");        Thread test4 = new Thread(new Runnable() {            public void run() {                demo4.test2();            }        }, "test4");        test1.start();        test2.start();        test3.start();        test4.start();    }

果然 test2 和 test4 不会马上调用

代码语言:javascript
复制
test1 : 4test3 : 4test3 : 3test1 : 3test3 : 2test1 : 2test3 : 1test1 : 1test1 : 0test3 : 0test2 : 4test4 : 4test4 : 3test2 : 3test2 : 2test4 : 2test2 : 1test4 : 1test4 : 0test2 : 0

现在使用多个对象来调用

代码语言:javascript
复制
public static void main(String[] args) {        // 采用Runable 复写 run        Thread test1 = new Thread(new Runnable() {            public void run() {                new demo4().test1();            }        }, "test1");        Thread test2 = new Thread(new Runnable() {            public void run() {                new demo4().test1();            }        }, "test2");        Thread test3 = new Thread(new Runnable() {            public void run() {                new demo4().test2();            }        }, "test3");        Thread test4 = new Thread(new Runnable() {            public void run() {                new demo4().test2();            }        }, "test4");        test1.start();        test2.start();        test3.start();        test4.start();    }

test4 是不会马上调用的,因为是类锁,不是对象锁

代码语言:javascript
复制
test1 : 4test3 : 4test2 : 4test3 : 3test1 : 3test2 : 3test2 : 2test3 : 2test1 : 2test1 : 1test2 : 1test3 : 1test2 : 0test1 : 0test3 : 0test4 : 4test4 : 3test4 : 2test4 : 1test4 : 0

对象锁防止在同一个时刻多个线程访问同一个对象的 synchronized 块。

如果不是同一个对象就没有这样子的限制。但是类锁就不同了。

synchronized 是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有 synchronized 块,注意这里是 “类的当前实例”, 类的两个不同实例就没有这种约束了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小刘IT教程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现 Runnable 接口
    • 通过继承 Thread 类本身
      • 并发后果
      • 对象锁
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档