多线程

1 啥是线程啊??

  • 线程是程序执行的一条路径,一个进程可以包含多条线程;
  • 多线程并发执行可以提高程序的效率,可以同时完成多项工作。

2 多线程的应用场景

  • 迅雷多线程下载
  • QQ多人视频
  • 服务器同时处理多个客户端请求

3 并发和并行

  • 并发:多核处理器同一时间运行不同的进程。
  • 并行:微观串行,宏观并行。

4 多线程实现方法

一 继承Tread类

具体步骤
  1. 需要开启线程的类继承Thread类;
  2. 重写run()方法,将需要执行的代码放在run()方法中;
  3. 在主方法中创建Thread类的子类对象;
  4. 调用对象的**start()**方法,开启线程。

p.s
不是调用run()方法,调用run方法相当于一个普通的方法,无法开启线程

二 实现Runnable接口

具体步骤
  1. 定义一个类实现Runnable接口;
  2. 重写run()方法,将需要执行的代码放在run()方法中;
  3. 在主方法中创建Runnable的子类对象;
  4. 创建Thread对象, 将子类对象作为参数传递给构造器;
  5. 调用Thread对象的start()方法开启线程。

两种方法的区别

  • 查看源码
  • 继承Thread:重写Thread中的run()方法,当调用了父类的start()方法时,直接找到子类的run()方法;
  • 实现Runnable接口:在Thread的构造器中传入Runnable的子类,在Thread的源码中有该成员变量,当调用Thread的start()方法时,会调用Thread的run()方法。该方法中会先判断Runnable子类是否为空,不为空即运行Runnable子类中的run()方法。
  • 继承Thread类
  • 好处:可以直接使用Thread中的对象,代码简单;
  • 弊端:如果已经有了父类,就不能使用该方法。
  • 实现Runnable接口
  • 好处:即使线程类有父类也可以实现Runnable接口,单继承多实现;
  • 弊端:不能直接使用Thread类中的方法,必须先声明后调用。

两种方法的选择

如果线程类没有父类,推荐继承Thread,代码简单。
如果线程类后期需要继承其他父类,实现Runnable,扩展性强。
两种方法在开发中都可使用,具体看需求。

5 使用匿名内部类实现线程的两种方式

//Method1
	new Thread() {
		public void run() {
			for(int i = 0; i < 1000 ; i++) {
				System.out.println("aaaaaaaaaaaaaaaaa");
			}
		}
	}.start();
	
	//Method2
	new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 0; i < 1000 ; i++) {
				System.out.println("bb");
			}
		}
	}).start();

6 多线程中的一些方法

1. 给线程改名

//Thread Method
	void setName();
	String getName();	//默认的名字是Thread-0以此类推
	
	//Constructor
	Thread(String name);
	Thread(Runnable target,String name);

2. 获取当前线程的对象(静态方法)

Thread.currentThread();		//静态方法,主线程也可以获取

3. 休眠进程 (静态方法)

Thread.sleep(long millis) ;		//静态方法,休眠millis毫秒,throws InterruptedException
tip
在run()方法中使用Thread.sleep()方法时只能捕捉异常不能抛出异常,因为父类的run()方法没有抛出异常,子类的run()方法无法抛出,只能catch
new Thread() {
		public void run() {
			for(int i = 0; i < 10; i++) {
				System.out.println(getName() + "..." + i);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {	
					e.printStackTrace();
				}
			}
		}
	}.start();

4.守护线程

守护线程的意思是,当非守护线程进行完毕后,守护线程也中断(但是不会立刻中断,有一定的延迟)。
比如打开QQ和聊天窗口,关闭QQ,聊天窗口也会自动关闭。

setDaemon(boolean on);		//当on为true,守护线程开启

5.加入线程

相当于插队。
void join();				//throws InterruptedException
tip

局部内部类访问局部变量必须用final修饰

6. 礼让线程

让其他线程先执行,让出CPU。
yeild();

7.设置优先级

setPriority(int P);		//值的范围1-10
	Thread.MAX_PRIORITY = 10;
	Thread.MIN_PRIORITY = 1;
	Thread.MAX_PRIORITY = 5;

7 同步(synchronized)

为什么需要同步

  • 当多线程并发运行时,多端代码同时执行时可能会被切换。我们希望在执行某一段代码时CPU不会切换到其他的线程,就需要同步。
  • 两段同步的代码在同一时间只能执行一段,另一段必须等结束后才会执行。

同步代码块

使用synchronized关键字加一个锁对象来定义一段代码。
锁对象可以是任意对象,但是不能用不同对象

synchronized (p) {			
	System.out.print("x");
	System.out.print("y");
	System.out.print("z");
	System.out.print("\r\n");
		}

同步方法

public synchronized void print1() {
	System.out.print("x");
	System.out.print("y");
	System.out.print("z");
	System.out.print("\r\n");
}

对于非静态方法,同步方法的锁对象是(this)
对于静态方法,同步方法的锁对象是该类的字节码文件(Xxx.class)

8 线程安全

  • Vector是线程安全的,ArrayList是线程不安全的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • Hashtable是线程安全的,HashMap是线程不安全的

案例:车站卖100票,四个售票窗口

注意点:

四个线程同时卖100张票,所以将Ticket类中的票数设置为静态变量,中间用同步代码块防止各进程出现错乱。
同步代码块的锁可以使用xxx.class。

public class Demo3_Ticket {
	//铁路售票100张,四个窗口卖完
	public static void main(String[] args) {
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
	}
}
class Ticket extends Thread{
	private static int ticket = 100;
	
	public void run() {
		while (true) {
			synchronized (Ticket.class) {
				if (ticket <= 0 ) {
					break;
				}
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "窗口:第" + ticket-- + "号票以售出。");
			}
		}
	}
}

用Runnable接口实现

注意点:

因为只需要创建一个runnable对象,所以ticket变量可以设置为非静态
同理,同步锁也可以设置为this

public class Demo4_Ticket {
	// Runnable接口实现
	public static void main(String[] args) {
		Ticket1 t = new Ticket1();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}

class Ticket1 implements Runnable {
	private int ticket = 100;

	@Override
	public void run() {
		while (true) {
			synchronized (this) {

				if (ticket <= 0) {
					break;
				}
				try {
					Thread.currentThread().sleep(10);
				} catch (InterruptedException e) {

					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "窗口:第" + ticket-- + "号票以售出。");
			}
		}
	}
}

9 死锁

private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
	new Thread() {
		public void run() {
			while(true) {
				synchronized(s1) {
					System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
					synchronized(s2) {
						System.out.println(getName() + "...拿到" + s2 + "开吃");
					}
				}
			}
		}
	}.start();
				
	new Thread() {
		public void run() {
			while(true) {
				synchronized(s2) {
					System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
					synchronized(s1) {
						System.out.println(getName() + "...拿到" + s1 + "开吃");
					}
				}
			}
		}
	}.start();
}

多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁。
应尽量避免同步代码块的嵌套。