多线程
1 啥是线程啊??
- 线程是程序执行的一条路径,一个进程可以包含多条线程;
- 多线程并发执行可以提高程序的效率,可以同时完成多项工作。
2 多线程的应用场景
- 迅雷多线程下载
- QQ多人视频
- 服务器同时处理多个客户端请求
3 并发和并行
- 并发:多核处理器同一时间运行不同的进程。
- 并行:微观串行,宏观并行。
4 多线程实现方法
一 继承Tread类
具体步骤
- 需要开启线程的类继承Thread类;
- 重写run()方法,将需要执行的代码放在run()方法中;
- 在主方法中创建Thread类的子类对象;
- 调用对象的**start()**方法,开启线程。
p.s
不是调用run()方法,调用run方法相当于一个普通的方法,无法开启线程
二 实现Runnable接口
具体步骤
- 定义一个类实现Runnable接口;
- 重写run()方法,将需要执行的代码放在run()方法中;
- 在主方法中创建Runnable的子类对象;
- 创建Thread对象, 将子类对象作为参数传递给构造器;
- 调用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();
}
多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁。
应尽量避免同步代码块的嵌套。