本帖最后由 小石姐姐 于 2018-6-8 09:25 编辑
多线程两种方式,建议用第二种方法,
实现方式1:定义一个类,让这个类继承Thread类,并且重写Thread里面的run方法.但是运行时需要调用的不是run方法,是start方法,它会执行run方法里面被重写的方法;
可以通过setName来更改当前线程名字,可以通过getName来获取当前线程名字
cpu执行程序时,在高速切换着线程,而且是随机的切换
实现方式2:为什么推荐使用这种方式呢,这种方式是通过implrments(实现)来实现多线程的,所以可以再implements的同时继承其他的类;也是为什么有两种多线程实现的原因
它的实现流程是创建一个类去implements(实现)Runnable接口;实现run方法,创建这个类的实例,创建一个Thread的对象(构造函数)来接收刚刚创建的类的对象,用Thread对此昂来调用start启动.
这时候自建类实现了Runable,但是不论是Runable还是咱们的自建类都没有对于线程操作的方法,这时候就需要用到Thread的静态方法了.
Thrwad的方法:
方法:static Thread currentThread();返回当前线程对象:举例:Thread t = Thread.currentThread();
然后还注意,可以共享一个Runnable的实现对象(也就是我们自定义的类).
方法:static void sleep(long millis);这个形参是毫秒;让当前线程休眠一会
多线程并发去访问同一个数据时就有可能发生问题,(看视频卖火车票案例);
关键词:synchronized,同步,可以修饰代码块和方法,被修饰的代码块和方法一旦被某个线程访问则直接锁住,其他线程将无法访问
同步代码块的使用:
synchronized(锁对象){
}
注意:锁对象需要被所有的线程共享
同步:安全性高,效率低
非同步:安全性低,效率高
关于同步方法
使用关键字synchronized修饰的方法,一旦被被一个线程访问则整个方法都会被锁死,其他线程无法访问
可以把有问题的代码用一个方法封装然后用synchronized进行修饰
,这就是一个同步方法,同步方法有默认的锁对象
非静态的同步方法默认锁对象是This.
静态的同步方法的默认锁对象是字节码对象
扩展(很重要)
另外一种同步方法:
JDK 5中新增的加锁方式: java.util.concurrent.locks.Lock接口
private Lock lock = new ReentrantLock(); // 创建锁对象, 该锁对象也要所有线程共享唯一一个
lock.lock(); //加锁
try {
// 需要同步的代码
} finally {
// 在finally中保证释放锁
lock.unlock(); //这里是释放锁,用的try{}finally就是为了保证可以在finally中释放锁
}
****这里要配合try{}finally一起使用
线程放锁3种情况:
1.同步代码块执行完毕, 同步方法执行结束, Lock调用unlock()
2.线程内部代码发生了异常, 导致线程终止
3.线程调用了wait()方法进行等待, 会立刻释放锁注意: Thread.sleep()不会释放锁
常见线程问题: 死锁
死锁 (dead lock): 同步代码块中的线程不出来, 也不释放锁; 同步代码块外的线程拿不到锁, 只能等在外面.
发生死锁的原因:
- 同步代码块内的线程, 可能处在死循环, IO阻塞, sleep()状态, 导致内部持有锁的线程无法出同步代码块
- 多个线程互相持有锁又不释放锁: 两个线程执行的任务都是双层同步代码块, 每层同步都需要一个锁, 两个线程中同步代码块的锁是相反的
死锁的结果: 程序卡死, 无法继续执行
如何避免死锁:
- 避免在同步代码块中执行死循环, IO阻塞操作, sleep()
- 避免多个线程互相持有锁又不释放锁的情况
代码演示:
/*
死锁的一种情况:
多个线程互相争夺对方持有的锁
*/
public class Demo {
// 定义2个锁对象
public static String lock1 = "{锁1}";
public static String lock2 = "{锁2}";
public static void main(String[] args) {
// 创建2个线程对象
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread1.setName("MyThread1");
thread2.setName("MyThread2");
// 开始执行
thread1.start();
thread2.start();
// 注意观察控制台的小红点
}
}
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println(getName() + "线程开始执行run()");
// 外层同步代码块, 使用锁1
synchronized (Demo.lock1) {
System.out.println(getName() + "线程拿到了" + Demo.lock1);
// 内层同步代码块, 使用锁2
System.out.println(getName() + "线程想要" + Demo.lock2);
synchronized (Demo.lock2) {
System.out.println("个别情况:" + getName() + "持有2把锁");
}
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
System.out.println(getName() + "线程开始执行run()");
// 外层同步代码块, 使用锁2
synchronized (Demo.lock2) {
System.out.println(getName() + "线程拿到了" + Demo.lock2);
// 内层同步代码块, 使用锁1
System.out.println(getName() + "线程想要" + Demo.lock1);
synchronized (Demo.lock1) {
System.out.println("个别情况:" + getName() + "持有2把锁");
}
}
}
}
线程之间的通信
java.lang.Object类
// 成员方法:
void wait(): 使当前线程处于等待状态, 并且会立刻释放锁
void notify(): 随机唤醒一个处于等待状态的线程
void notifyAll(): 唤醒所有处于等待状态的线程
// 这三个方法必须在同步代码块中用锁对象来调用, 否则会抛IllegalMonitorStateException异常
-----------------------------------------------------
sleep()和wait()的区别:
- 让当前线程在指定时间内睡眠, 时间结束后继续执行
- 是Thread类的静态方法
- 不会释放锁
- 让当前线程等待, 直到有人唤醒
- 是Object类的非静态方法
- 会立刻释放锁
注意: * wait的线程必须要有某个线程调用了notify才能唤醒, 如果所有的线程都wait了, 那程序整个就全都在等待了. 所以在写代码时必须考虑仔细
|