创建线程的第一种方式:继承Thread类
创建线程的第二种方式:实现Runnable接口
1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类中的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成对象。
2.避免了java单继承的局限性。
线程安全问题产生的原因:
1.多个线程在操作共享的数据
2.操作共享数据的线程代码有多条
当一个线程在执行操作共享数据的多条代码时,其他线程参与了运算。就会导致线程安全问题的产生。
解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他代码才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
同步函数的锁是this
同步函数和同步代码块的区别:
同步函数的锁是固定的this,同步代码块的锁是任意的对象。建议使用同步代码块。
静态的同步函数使用的锁是该函数所属的字节码文件对象。可以用getClass方法获取,也可以用 当前类名.class表示。
1 死锁 2 class DeadLock 3 { 4 public static void main(String[] args) 5 { 6 Test a=new Test(true); 7 Test b=new Test(false); 8 Thread t1=new Thread(a); 9 Thread t2=new Thread(b); 10 t1.start(); 11 t2.start(); 12 } 13 } 14 15 16 class Test implements Runnable 17 { 18 private final boolean flag; 19 public Test(boolean flag) 20 { 21 this.flag=flag; 22 } 23 public void run() 24 { 25 if(flag) 26 { 27 while(true) 28 { 29 synchronized(MyLock.locka) 30 { 31 System.out.println(Thread.currentThread().getName()+"-----if----locka"); 32 synchronized(MyLock.lockb) 33 { 34 System.out.println(Thread.currentThread().getName()+"-----if----lockb"); 35 } 36 } 37 } 38 } 39 else 40 { 41 while(true) 42 { 43 synchronized(MyLock.lockb) 44 { 45 System.out.println(Thread.currentThread().getName()+"-----else----lockb"); 46 synchronized(MyLock.locka) 47 { 48 System.out.println(Thread.currentThread().getName()+"-----else----locka"); 49 } 50 } 51 } 52 } 53 54 } 55 } 56 57 58 59 60 61 class MyLock 62 { 63 public static final Object locka=new Object(); 64 public static final Object lockb=new Object(); 65 }