1 理解线程、创建线程
1.1 线程:程序中某一条执行线索
1.2 创建线程的方式
继承Thread和实现Runnale接口
/**
* Description:两种创建线程的方法,extends Thread和 implements Runnable
*/
// 1. 通过extends继承Thread
class CreateThread1 extends Thread {
@Override
public void run() {
while (true) {
System.out.println("This is " +
Thread.currentThread().getName());
}
}
}
// 2. 通过implements实现runnable接口,创建资源对象
class CreateThread2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("This is " +
Thread.currentThread().getName());
}
}
}
public class TestCreateThread {
public static void main(String[] args) {
//以第一种方式创建线程并启动 (extends Thread)
CreateThread1 thread1 = new CreateThread1();
thread1.start();
//以第二种方式创建线程并启动 (implements Runnale)
CreateThread2 t = new CreateThread2();
Thread thread2 = new Thread(t);
thread2.start();
while (true) {
System.out.println("This is " +
Thread.currentThread().getName());
}
}
}
1.3 两种创建线程方法的对比
1 Extends创建对象:通过new Thread()方式直接创建线程对象,会直接产生一个该线程访问的资源对象
2 Implements创建对象:只能通过new Thread(Runnable target)方式创建对象,只要target对象是同一个,那么创建出来的线程访问的资源对象都相同
3 相比之下,实现Runnale接口比继承Thread类会有以下几点优势:
i 适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想
ii 避免了Java单继承机制带来的局限,可以实现“把继承了某一父类的子类放入线程中”
iii 有利于程序的健壮性,某一资源代码可以被多个线程共享,代码与数据是独立的。多个线程可以操作相同的数据,与它们的代码无关。
1.4 后台线程、联合线程(了解)
1 后台线程:后台线程是相对前台线程而言的,正常创建并启动的是前台线程,如果在某一线程调用start()启动前,调用了setDaemon(true)方法,这个线程就变成了后台线程
2 联合线程:在某一线程T在执行时,调用另一个正在执行的线程otherThread的join()方法,将另外一个线程合并到自己的线程上,otherThread会合并到T线程上,并在该线程上执行,这时候T线程会暂停执行,这就是联合线程
T线程什么时候恢复执行?
i 当otherThread执行完毕并终止,T线程才会继续执行
ii 指定的join(time)中的time时间到,会自动分离两个线程,T线程继续执行
2 线程同步
线程安全:多个线程共享同一个资源,在对必须看成整体的某个代码块(具有原子性的代码块),进行操作时,操作还未完成就被打断,会造成数据的前后不一致,因此造成线程的不安全
解决方法:线程同步——同步代码块、同步函数 关键字Synchronized
在这里必须引入某些概念:
1. java的每个对象都具有一个锁旗标(俗:锁标记)
2. Synchronized检查的对象被称为监视器
3. 某线程要进入Synchronized修饰的同步代码块或同步函数,必须持有监视器的锁旗标:
i 如果经检查该线程拥有监视器的锁旗标,该线程可以进入同步代码块或同步函数并执行,当执行完毕,会自动释放本监视器的锁旗标,同时,有可能再次获取到本监视器的锁旗标
ii 如果经检查该线程没有监视器的锁旗标,该线程不被允许进入同步代码块或同步函数,会被分配到与该监视器相关联的锁池中,并等待机会获取锁旗标,执行代码
iii 如果某线程本身持有其他对象的锁旗标,但并不具备本监视器的锁旗标,同样会被加入本监视器的锁池,并且其自身拥有的锁旗标不会释放
- 死锁:
i A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标
ii B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标
iii 结果:双方都抱着自己所拥有的锁不放,从而线程无法继续
Exp:
/**
* DeadLock:
* (1) A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标
* (2) B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标
* res:
* 双方都抱着自己所拥有的锁不放,从而线程无法继续
*/
class A implements Runnable {
static byte[] t1 = new byte[0];
@Override
public void run() {
Thread.currentThread().setName("Thread A");
//(1)A线程拥有T1监视器的锁旗标
synchronized (t1) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" attend to call B.t2");
//企图获取B线程所拥有的T2监视器的锁旗标
synchronized (B.t2) {
System.out.println(B.t2);
}
}
}
}
class B implements Runnable {
static byte[] t2 = new byte[0];
@Override
public void run() {
Thread.currentThread().setName("Thread B");
//B线程拥有T2监视器的锁旗标
synchronized (t2) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" attend to call A.t1");
//企图获取B线程所拥有的T2监视器的锁旗标
synchronized (A.t1) {
System.out.println(A.t1);
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
new Thread(new A()).start();
new Thread(new B()).start();
}
}
3 线程通信
生产者消费者问题:有什么问题?
3.1线程安全
问题描述:若生产过程或消费过程被打断,数据会产生不一致
解决方法:所以必须对生产过程和消费过程加锁
3.2并发执行
问题描述:
(1)若生产过快,消费会漏掉某些生产品
(2)若消费过快,会产生某一产品被消费多次(现实是不可能事件)
解决方法:
现实生活中,如果要做到生产1个消费一个,那么
(1) 当生产过快,那么生产者每生产一个就必须停下来,等消费者消费完一个,再继续执行生产
(2) 当消费过快,那么消费者每消费一个就必须停下来,等生产者生产完一个,再继续执行消费(不得不这样)
程序中,可通过wait(),notify,notifyAll,模拟实现
3.3 模拟生产者消费者问题
Exp:
/**
*生产者消费者问题
**/
class Product {
private int pno = 0;
private String pname = null;
private boolean isfull = false;
public synchronized void putProduct(int pno, String pname) {
//若生产满了、即生产过快,那么使生产者暂停生产
if(isfull) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.pno = pno;
//人为制造意外,生产过程未完成就被打断
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pname = pname;
//每生产一个就显示出来
System.out.println("Producer make " + pname);
isfull = true;
notify();
}
public synchronized void getProduct() {
if(!isfull) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Consumer pick " + pname);
isfull = false;
notify();
}
}
class Producer implements Runnable {
private Product p = null;
private int i = 1;
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
while(true) {
if(1 == i % 2) {
i++;
p.putProduct(1, "Product1");
} else {
i++;
p.putProduct(2, "Product2");
}
}
}
}
class Consumer implements Runnable {
Product p = null;
public Consumer(Product p) {
this.p = p;
}
@Override
public void run() {
while(true) {
p.getProduct();
}
}
}
public class TestProCon {
public static void main(String[] args) {
Product p = new Product();
Producer producer = new Producer(p);
Consumer consumer = new Consumer(p);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
Res:
Producer make Product1
Consumer pick Product1
Producer make Product2
Consumer pick Product2
……
3.4 wait(),notify,notifyAll
wait()方法,谁调用它,谁就沉默,沉默就放弃自己拥有的锁旗标
notify()方法,唤醒同一对象监视器中调用wait()的第一个线程
notifyAll()方法,唤醒同一对象监视器中调用 wait()的所有线程,被唤醒的线程会加入锁池中,等待锁旗标,一旦拥有锁旗标就进入Runnable状态
4 线程的生命控制
4.1线程的生命周期
1 初始状态,线程创建,线程对象调用 start() 方法。
2 可运行状态,也就是等待 Cpu 资源(os调度),等待运行的状态。
3 运行状态,获得了 cpu 资源,正在运行状态。
4 阻塞状态,也就是让出 cpu 资源,进入一种等待状态,而且不是可运行状态,有三种情况会进入阻塞状态。
i 如等待输入(输入设备进行处理,而 CPU 不处理),则放入阻塞,直到输入完毕,阻塞结束后会进入可运行状态。
ii 线程休眠,线程对象调用 sleep() 方法,阻塞结束后会进入可运行状态。
iii 线程对象 2 调用线程对象 1 的 join() 方法,那么线程对象 2 进入阻塞状态,直到线程对象 1 中止。
5 中止状态,也就是执行结束。
6 锁池状态
7 等待队列
4.2 控制线程的生命
(用boolean型的flag)
5 避免无谓的线程控制
Exp:
class Test {
private byte[] resource1 = new byte[0];
private byte[] resource2 = new byte[0];
public synchronized void method1() {
//处理resource1
}
public synchronized void method2() {
//处理resource1
}
public synchronized void method3() {
//处理resource2
}
public synchronized void method4() {
//处理resource2
}
}
分析:
已知:
method1和method2都是对resource1进行处理,需要同步控制
method3和method4都是对resource1进行处理,需要同步控制
method1和method3、4,method2和method3、4处理的对象不同,它们之间本可以一起进行,是互不影响
但是此程序将监视器对象设置为调用对象this本身,那么在执行1的时候,3和4也是无法拿到监视器的锁,造
成3、4无法进行、这就是无谓的同步控制
Alter:
class Test {
private byte[] resource1 = new byte[0];
private byte[] resource2 = new byte[0];
private byte[] lock1 = new byte[0];
private byte[] lock2 = new byte[0];
public void method1() {
//处理resource1
synchronized (lock1) {
}
}
public void method2() {
//处理resource1
synchronized (lock1) {
}
}
public void method3() {
//处理resource2
synchronized (lock2) {
}
}
public void method4() {
//处理resource2
synchronized (lock2) {
}
}
}
分析:
通过创建两个不同的对象当监视器,使method1和method2关联lock1,method3和method4关联lock2
从而避免了无谓的同步控制,使程序性能得以提升