Java笔记(十九)……多线程
概述
进程:
是一个正在执行中的程序
每一个进程执行都有一个执行顺序,该执行顺序是一个执行路径,或者叫一个控制单元
线程:
就是进程中的一个独立的控制单元,线程在控制着进程的执行
一个进程中至少有一个线程
Java JVM启动的时候会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程,与C类似,java.exe相当于所有进程的父进程,而且JVM启动了不止一个线程,还有负责垃圾回收的线程
创建线程
第一种创建方式:继承Thread类
1: class Sell extends Thread
2: {
3: private int tickets = 100;
4: Sell()
5: {
6: //启动线程
7: start();
8: }
9: //复写Thread类中的run方法,将线程运行所需的代码写到run方法中
10: public void run()
11: {
12: while(tickets > 0)
13: {
14: System.out.println("tickets = "+tickets--);
15: }
16: }
17: }
第二种创建方式:实现Runnable接口
1: class Sell implements Runnable
2: {
3: private int tickets = 100;
4:
5: //实现Runnable接口的run方法
6: public void run()
7: {
8: while(tickets > 0)
9: {
10: System.out.println("tickets = "+tickets--);
11: }
12: }
13: }
14: class ThreadDemo
15: {
16: public static void main(String[] args)
17: {
18: //将Runnable接口的子类Sell传递给Thread的构造函数,并启动线程
19: new Thread(new Sell()).start();
20: }
21: }
实现方式与继承方式的区别
其实Thread类同样也是实现了Runnable接口,所以我们要的只是run方法,那么我们只要实现Runnable接口即可,这样避免了单继承的局限性,我们在实现Runnable接口的同时还可以继承其他的类,扩展了功能
线程权限问题
线程安全问题--同步
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误
1:
2: class Sell implements Runnable
3: {
4: private int tickets = 10;
5:
6: //实现Runnable接口的run方法
7: public void run()
8: {
9: while(tickets > 0)
10: {
11: try{Thread.sleep(500);}catch(Exception e){}
12: System.out.println(Thread.currentThread().getName()+"..tickets = "+(--tickets));
13: }
14: }
15: }
当我们让线程操作共享数据时,暂停一会,可以发现,每个线程很容易-1剩余票数的错误
解决方法
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不能参与进来执行
Java对多线程的安全问题提供了专业的解决方式,就是同步代码块或者同步函数
synchronized(对象)
{
需要被同步的代码
}
synchronized void show()
{
函数内容
}
1: class Sell implements Runnable
2: {
3: private int tickets = 10;
4:
5: //实现Runnable接口的run方法
6: public void run()
7: {
8: //加入锁,使代码同步
9: synchronized(this)
10: {
11: while(tickets > 0)
12: {
13: try{Thread.sleep(500);}catch(Exception e){}
14: System.out.println(Thread.currentThread().getName()+"..tickets = "+(--tickets));
15: }
16: }
17: }
18: }
我们可以看到,即使中途有睡眠过程,也不再出现错误票数,当然这个例子有一些问题,虽然保证了安全,却只能让一个线程完成操作,解决方法很简单,在while循环内设置判断语句,再加锁即可
对象如同锁,持有锁的线程可以在同步中执行
没有持有锁的线程即使获取了cpu的执行权,也无法执行同步代码
如果同步函数被静态修饰后,如何使用锁?
通过验证,发现不再是this,因为静态方法中也不可以定义this
静态进入内存时,内存中没有本类对象,但是一定有该类的对应的字节码文件对象 类名.class,该对象的类型是Class
所以静态的同步方法是,使用该方法所在类的字节码对象作为锁,即类名.class
1: class Tickets
2: {
3: static int tickets = 10;
4: public static void sell()
5: {
6: //类对象作为锁
7: synchronized(Selling.class)
8: {
9: if(tickets > 0)
10: {
11: try{Thread.sleep(500);}catch(Exception e){}
12: System.out.println(Thread.currentThread().getName()+"..tickets = "+(--tickets));
13: }
14: }
15: }
16: }
17:
18: class Selling implements Runnable
19: {
20: //实现Runnable接口的run方法
21: public void run()
22: {
23: while(Tickets.tickets> 0)
24: {
25: Tickets.sell();
26: }
27: }
28: }
同步的前提:
- 必须要有两个或两个以上的线程
- 必须要多个线程使用同一个锁
同步的利弊
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源
加入同步之后线程的状态如下
如何编写多线程
- 明确哪些代码是多线程运行代码
- 明确共享数据
- 明确多线程运行代码中哪些语句是操作共享数据的
死锁
死锁其实就是同步中嵌套同步
1:
2: class A implements Runnable
3: {
4: String a = "a";
5:
6: public void run()
7: {
8: while(true)
9: {
10: //A抢占A锁
11: synchronized(A.class)
12: {
13: System.out.println("A get 1");
14: //A抢占B锁
15: synchronized(B.class)
16: {
17: System.out.println("A get 2");
18: System.out.println("A:"+a);
19: }
20: }
21: }
22: }
23: }
24:
25: class B implements Runnable
26: {
27: String b = "b";
28:
29: public void run()
30: {
31: while(true)
32: {
33: //B抢占B锁
34: synchronized(B.class)
35: {
36: System.out.println("B get 1");
37: //B抢占A锁
38: synchronized(A.class)
39: {
40: System.out.println("B get 2");
41: System.out.println("B:"+b);
42: }
43: }
44: }
45: }
46: }
47: class DeadlockDemo
48: {
49: public static void main(String[] args)
50: {
51: new Thread(new A()).start();
52: new Thread(new B()).start();
53: }
54: }