1. java中实现多线程的两种方式
1 //第一种:继承Thread类,重写run()方法 2 class ThreadTest1 extends Thread{ 3 public void run(){ 4 String threadName = Thread.currentThread().getName(); 5 for(int i=0;i<10;i++){ 6 System.out.println("ThreadTest1 "+threadName+" running ... "+i); 7 } 8 } 9 } 10 11 //第二种:实现Runnable接口,重写run()方法 12 class ThreadTest2 implements Runnable{ 13 public void run(){ 14 String threadName = Thread.currentThread().getName(); 15 for(int i=0;i<10;i++){ 16 System.out.println("ThreadTest2 "+threadName+" running ... "+i); 17 } 18 } 19 } 20 21 public class Thread01{ 22 public static void main(String[] args){ 23 ThreadTest1 t1 = new ThreadTest1(); 24 ThreadTest1 t2 = new ThreadTest1(); 25 Thread t3 = new Thread(new ThreadTest2()); //注意 26 Thread t4 = new Thread(new ThreadTest2()); 27 28 //start() 启动线程 29 t1.start(); 30 t2.start(); 31 t3.start(); 32 t4.start(); 33 } 34 }
归根结底,两种方法都是用Thread类或其子类对象调用start()方法启动线程。
为什么用start()方法而不用run()方法启动线程呢?因为start()方法产生了运行这个线程所需的系统资源,安排其运行,并调用线程体(run()方法)。
一个线程只能启动一次,不管调用多少次start()方法,结果也只用一个线程。
2. 线程共享资源
建议实现Runnable接口,其好处是:
- 多线程之间可以共享资源
- 避免单继承带来的问题
- 数据和代码独立
1 class ThreadTest2 implements Runnable{ 2 private int count=20; //共享资源 3 public void run(){ 4 String threadName = Thread.currentThread().getName(); 5 while(count>0){ 6 System.out.println("ThreadTest2 "+threadName+" 售出 "+count+" 号票"); 7 count--; 8 } 9 } 10 } 11 12 public class ThreadDemo2{ 13 public static void main(String[] args){ 14 ThreadTest2 t2 = new ThreadTest2(); 15 (new Thread(t2,"窗口1")).start(); 16 (new Thread(t2,"窗口2")).start(); 17 (new Thread(t2,"窗口3")).start(); 18 (new Thread(t2,"窗口4")).start(); 19 } 20 }
运行结果:
ThreadTest2 窗口1 售出 20 号票 ThreadTest2 窗口1 售出 19 号票 ThreadTest2 窗口3 售出 20 号票 ThreadTest2 窗口2 售出 20 号票 ThreadTest2 窗口3 售出 17 号票 ThreadTest2 窗口1 售出 18 号票 ThreadTest2 窗口1 售出 14 号票 ThreadTest2 窗口4 售出 19 号票 ThreadTest2 窗口1 售出 13 号票 ThreadTest2 窗口3 售出 15 号票 ThreadTest2 窗口3 售出 10 号票 ThreadTest2 窗口2 售出 16 号票 ThreadTest2 窗口2 售出 8 号票 ThreadTest2 窗口3 售出 9 号票 ThreadTest2 窗口1 售出 11 号票 ThreadTest2 窗口4 售出 12 号票 ThreadTest2 窗口1 售出 5 号票 ThreadTest2 窗口3 售出 6 号票 ThreadTest2 窗口2 售出 7 号票 ThreadTest2 窗口3 售出 2 号票 ThreadTest2 窗口1 售出 3 号票 ThreadTest2 窗口4 售出 4 号票 ThreadTest2 窗口2 售出 1 号票
问题:多个窗口售出了相同编号的票,发生了访问冲突。
3. 线程同步
同步synchronized意:协同步调,按照先后顺序进行;同步代码块;同步函数
前提:2个或2个以上的线程;使用同一把锁
作用:保证同步中只有一个线程在运行。
好处:解决线程安全问题
弊端:多个线程需要判断锁,消耗资源
在Java里面,同步锁的概念就是这样的。任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。 解决方案: 1)同步代码块 synchronized(类或对象) { 需要同步的代码段 } 2)同步函数 (非static的情况) public synchronized void fun() { 代码段 } 等价于 public void fun() { synchronized(this) { 代码段 } } 调用此同步函数的对象作为此同步函数的同步锁。 (static的情况) public static synchronized void fun() { 代码段 } 静态变量或静态方法加载到内存中时,内存中没有本类对象,但一定有了该类对应的 字节码文件(类名.class),该对象的类型是class。静态同步函数使用的同步锁是所在类 的字节码文件。
1 class ThreadTest implements Runnable 2 { 3 private int ticket=50; 4 public void run(){ 5 while(ticket>0){ 6 String threadName = Thread.currentThread().getName(); 7 //同步代码块(越小越好) 8 synchronized(this){ 9 if(ticket>0){ 10 System.out.println(threadName + " sales ticket "+ticket); 11 ticket--; 12 } 13 } 14 } 15 } 16 } 17 18 public class Thread03{ 19 public static void main(String[] args){ 20 ThreadTest t = new ThreadTest(); 21 (new Thread(t, "窗口A")).start(); 22 (new Thread(t, "窗口B")).start(); 23 (new Thread(t, "窗口C")).start(); 24 (new Thread(t, "窗口D")).start(); 25 } 26 }
运行结果:
窗口A sales ticket 50 窗口D sales ticket 49 窗口D sales ticket 48 窗口D sales ticket 47 窗口D sales ticket 46 窗口D sales ticket 45 窗口D sales ticket 44 窗口D sales ticket 43 窗口D sales ticket 42 窗口D sales ticket 41 窗口D sales ticket 40 窗口D sales ticket 39 窗口D sales ticket 38 窗口D sales ticket 37 窗口D sales ticket 36 窗口D sales ticket 35 窗口D sales ticket 34 窗口D sales ticket 33 窗口D sales ticket 32 窗口D sales ticket 31 窗口D sales ticket 30 窗口D sales ticket 29 窗口D sales ticket 28 窗口D sales ticket 27 窗口D sales ticket 26 窗口D sales ticket 25 窗口D sales ticket 24 窗口D sales ticket 23 窗口D sales ticket 22 窗口D sales ticket 21 窗口D sales ticket 20 窗口D sales ticket 19 窗口D sales ticket 18 窗口D sales ticket 17 窗口D sales ticket 16 窗口D sales ticket 15 窗口D sales ticket 14 窗口D sales ticket 13 窗口D sales ticket 12 窗口D sales ticket 11 窗口D sales ticket 10 窗口D sales ticket 9 窗口D sales ticket 8 窗口D sales ticket 7 窗口D sales ticket 6 窗口D sales ticket 5 窗口D sales ticket 4 窗口D sales ticket 3 窗口D sales ticket 2 窗口D sales ticket 1
4. 线程死锁
死锁现象即相互等待的局面
(操作系统中的死锁) 指2个或多个进程在执行过程,因竞争资源而造成一种互相等待的局面,若无外力干涉,进程无法推进下去。
发生死锁的原因一般是两个对象的锁相互等待造成的。
那么为什么会产生死锁呢?
- 因为系统资源不足。
- 进程运行推进的顺序不合适。
- 资源分配不当。
产生死锁的条件有四个:
- 互斥条件:所谓互斥就是进程在某一时间内独占资源。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
5. 单例模式
特点:1.私有构造函数 2.在类中创建一个指向自己实例的私有静态引用 3.以自己实例为返回类型值的静态公有方法。
//饿汉式 class Singleton{ private static final Singleton singleInstance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleInstance; } }
//懒汉式 class Singleton{ private static Singleton singleInstance =null; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleInstance==null){ singleInstance = new Singleton(); } return singleInstance; } }
6. 线程通信(wait()和notify()、notifyAll())
线程的状态
7. ReentrantLock和Condition
8. 停止线程的方法(interrupt()和isInterrupt())
9. 守护线程和join方法
守护线程是为其他线程提供便利服务的,当全部的用户线程结束后,守护线程才会随JVM结束工作。 thread.setDaemon(true);
public static void main(String[] args){ Thread t3 = new Thread(test2, "线程t3"); t3.start(); t3.join(); //主线程就此陷入等待,直到t3线程结束。 }
10. 线程优先级和yield方法
线程的优先级从低到高:1-10,优先级高的的优先执行,每个新线程都继承了父线程的优先级,常量:Thread.MIN_PRIORITY 值为1,Thread.MAX_PRIORITY 值为10,Thread.NORM_PRIORITY 值为5
void setPriority(priority) int getPriority()
yield()方法将线程从执行状态变成就绪状态。