Java坑爹玩意儿之-多线程

来源:互联网 发布:进入国外网站软件 编辑:程序博客网 时间:2024/06/05 15:28

线程这玩意儿我也不是很懂,一边学习一边写这个系列。谈到多线程,总是会提到线程和进程,还有同步机制等等,下面是我对它们的理解:

进程:操作系统中有很多进程,比如说QQ,IDEA,网易云音乐,这些都是进程。
线程:一个进程包含至少一个线程。

线程的创建

在JDK1.5问世后,我们有三种创建线程的方法。

1. 继承Thread类,重写run方法

public class Main {    public static void main(String[] args){        A a=new A();        a.start();    }}class  A extends Thread{    @Override    public void run() {            for (int i=0;i<10;i++){                System.out.println(i);            }        }}

out put:
0
1
2
3
4
5
6
7
8
9

2. 实现Runnable接口,重写run方法

public class Main {    public static void main(String[] args){        A a=new A();        Thread t=new Thread(a);        t.start();    }}class  A implements Runnable{    @Override    public void run() {            for (int i=0;i<10;i++){                System.out.println(i);            }        }}

output:
0
1
2
3
4
5
6
7
8
9

3. 实现Callable接口

public class Main {    public static void main(String[] args){        A callable=new A();        ExecutorService executorService= Executors.newFixedThreadPool(1);        executorService.submit(callable);    }}class A implements Callable{    @Override    public Object call() throws Exception {        for (int i=0;i<10;i++){            System.out.println(i);        }        return null;    }}

out put:
0
1
2
3
4
5
6
7
8
9

Java Main方法相关小知识:

public class Main {    public static void main(String[] args){        A a=new A();        Thread thread=new Thread(a);        thread.start();        for (int i=0;i<150;i++){            System.out.println("********");        }    }}class A implements Runnable{    @Override    public void run() {        for (int i=0;i<150;i++){            System.out.println("************************");        }    }}

end of output(这里我只截取了结尾的输出):
********
********
********
********
********
********
********
********
********
************************
************************
************************
************************
************************
************************

IMPORTANT:由输出可得,java的main方法中,也是多线程运作的,main线程和我们new出来的线程切换运行,才可能得到这样的结果,否则应该是150个短的和150个长的,而不会这么断开。

线程的生命周期

  1. 创建线程对象(new):Thread t=new Thread()
  2. 就绪(ready):t.start()
  3. 运行(go):run()
  4. 阻塞(blocked):sleep(), wait()
  5. 死亡(dead):stop()
    yield():如果你的线程目前是运行的状态,调用这个方法会使之回到就绪状态。
    wait():会让当前线程进入等待池,只有notify()才能让它回归到就绪状态。

线程的同步

Synchronized用来修饰代码块或者方法时,能够保证同一时刻只有一个线程在执行这段代码/方法。

修饰方法

public class Main {    public static void main(String[] args){        A a=new A();        Thread thread=new Thread(a,"A");        thread.start();        Thread thread1=new Thread(a,"B");        thread1.start();    }}class A implements Runnable{    @Override    public synchronized void run() {        for (int i=0;i<10;i++){            System.out.println(Thread.currentThread().getName()+":"+i);        }    }}

output:
A:0
A:1
A:2
A:3
A:4
A:5
A:6
A:7
A:8
A:9
B:0
B:1
B:2
B:3
B:4
B:5
B:6
B:7
B:8
B:9

结论:多个线程去调用同一个对象(本例中的a)的同步方法,只有等一个线程跑完整个方法才会轮到下一个线程去跑这个方法。

修饰代码块

public class Main {    public static void main(String[] args){        A a=new A();        Thread thread=new Thread(a,"A");        thread.start();        Thread thread1=new Thread(a,"B");        thread1.start();    }}class A implements Runnable{    @Override    public void run() {        synchronized(this) {            for (int i = 0; i < 10; i++) {                System.out.println(Thread.currentThread().getName() + ":" + i);            }        }    }}

out put:
A:0
A:1
A:2
A:3
A:4
A:5
A:6
A:7
A:8
A:9
B:0
B:1
B:2
B:3
B:4
B:5
B:6
B:7
B:8
B:9

结论:多个线程去调用同一个对象(本例中的a)的同步代码块,只有等一个线程跑完整个代码块才会轮到下一个线程去跑这个代码块。

接下来的例子:多线程访问同类不同对象的同步代码块

public class Main {    public static void main(String[] args){        A a=new A();        A b=new A();        Thread thread=new Thread(a,"A");        thread.start();        Thread thread1=new Thread(b,"B");        thread1.start();    }}class A implements Runnable{    @Override    public void run() {        synchronized(this) {            for (int i = 0; i < 10; i++) {                System.out.println(Thread.currentThread().getName() + ":" + i);            }        }    }}

out put:
A:0
B:0
B:1
B:2
B:3
A:1
B:4
A:2
B:5
A:3
A:4
A:5
A:6
A:7
A:8
A:9
B:6
B:7
B:8
B:9

结论:多个线程访问同类不同对象的同步代码块,并不会阻塞,该切换的照样切换。

那么问题来了,如果一个类同时拥有同步方法和非同步方法,在访问同一对象的这两个方法时,多线程又会如何表现?

public class Main {    public static void main(String[] args){        ThreadImpl a=new ThreadImpl();        Thread thread=new Thread(a,"A");        thread.start();        Thread thread1=new Thread(a,"B");        thread1.start();    }}class ThreadImpl implements Runnable{    @Override    public void run() {            A();            B();    }    public synchronized void A(){        for (int i = 0; i < 10; i++) {            System.out.println("Thread:"+Thread.currentThread().getName()+"sync");        }    }    public void B(){        for (int i = 0; i < 10; i++) {            System.out.println("Thread:"+Thread.currentThread().getName());        }    }}

output
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:Async
Thread:A
Thread:A
Thread:A
Thread:A
Thread:A
Thread:A
Thread:A
Thread:A
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:Bsync
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:B
Thread:A
Thread:A

结论:一个线程调用同一对象的同步方法时,另一个线程可调用其非同步方法。

public class Main {    public static void main(String[] args){        ThreadImpl a=new ThreadImpl();        Thread thread=new Thread(a,"A");        thread.start();        Thread thread1=new Thread(a,"B");        thread1.start();    }}class ThreadImpl implements Runnable{    @Override    public void run() {            A();            B();    }    public synchronized void A(){        for (int i = 0; i < 10; i++) {            System.out.println("Thread"+Thread.currentThread().getName()+"------syncA");        }    }    public synchronized void B(){        for (int i = 0; i < 10; i++) {            System.out.println("Thread"+Thread.currentThread().getName()+"------syncB");        }    }}

output:
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncA
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadA——syncB
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncA
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB
ThreadB——syncB

结论:多线程访问同个对象的多个同步方法,当线程A在访问某个同步方法时,其他线程无法访问到任何同步方法。

具体例子:银行存款

提要:如果多个线程对一个存款方法进行调用,那么按照我们的想法,Java按照线程顺序依次调用同步方法,而非同步的方法是切换调用的。

import java.text.SimpleDateFormat;import java.util.Date;import java.util.logging.Logger;public class BankAccount {    private String id;    private int amount;    private String lastModifyTime;    private final static Logger logger=Logger.getLogger("demo log");    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public int getAmount() {        return amount;    }    public void setAmount(int amount) {        this.amount = amount;    }    public boolean saveMoney(int money){        amount+=money;        Date lastModifyTime=new Date();        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");        this.lastModifyTime=simpleDateFormat.format(lastModifyTime);        logger.info(this.lastModifyTime+"   amount:"+this.amount);        return true;    }    public static void main(String[] args){        BankAccount bankAccount = new BankAccount();        Thread thread1=new Thread(new Runnable() {            @Override            public void run() {                bankAccount.saveMoney(100);            }        });        Thread thread2=new Thread(new Runnable() {            @Override            public void run() {                bankAccount.saveMoney(100);            }        });        thread1.start();        thread2.start();    }}

output:
四月 24, 2017 8:59:17 下午 BankAccount saveMoney
信息: 2017-04-24-20:59:17 amount:200
四月 24, 2017 8:59:17 下午 BankAccount saveMoney
信息: 2017-04-24-20:59:17 amount:200

上述代码,每次存完的总额都是200,这是不正确的。

import java.text.SimpleDateFormat;import java.util.Date;import java.util.logging.Logger;public class BankAccount {    private String id;    private int amount;    private String lastModifyTime;    private final static Logger logger=Logger.getLogger("demo log");    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public int getAmount() {        return amount;    }    public void setAmount(int amount) {        this.amount = amount;    }    public synchronized boolean saveMoney(int money){        amount+=money;        Date lastModifyTime=new Date();        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");        this.lastModifyTime=simpleDateFormat.format(lastModifyTime);        logger.info(this.lastModifyTime+"   amount:"+this.amount);        return true;    }    public static void main(String[] args){        BankAccount bankAccount = new BankAccount();        Thread thread1=new Thread(new Runnable() {            @Override            public void run() {                bankAccount.saveMoney(100);            }        });        Thread thread2=new Thread(new Runnable() {            @Override            public void run() {                bankAccount.saveMoney(100);            }        });        thread1.start();        thread2.start();    }}

output:
四月 24, 2017 9:07:45 下午 BankAccount saveMoney
信息: 2017-04-24-21:07:45 amount:100
四月 24, 2017 9:07:45 下午 BankAccount saveMoney
信息: 2017-04-24-21:07:45 amount:200

上述代码,第一次存完是100,第二次存完是200,正确。

总结:
1. 对于拥有同步方法的类,其每个实例都有一把自己的锁,我们称之为“对象锁”,一旦某个线程想要调用任一同步方法,必须先取到这个锁,直到执行完这个方法才放开这把锁。只有这样,才能保证,对于同一实例,同一时刻只能执行一个同步方法,而这个同步方法只能被一个线程访问。
2. 关于sleep,如果在一个同步方法/代码块内调用sleep,当前线程也不会放开对象锁,其他线程依旧无法调用这个对象的任何同步方法和同步代码块
3. 关于wait,如果在一个同步方法/代码块内调用该对象的wait,当前线程会放开对象锁,其他线程可以调用这个对象的任何同步方法和同步代码块

0 0
原创粉丝点击