多线程
1、介绍
线程是同一进程内同时执行的多个代码段。宏观上并行,微观上串行,对于每块CPU来说,同一时刻,CPU只能执行同一条指令,但是对于多核系统来说,可以做到真正的并行。线程间可以共享内存,进程间不能共享内存。
2、创建线程的方式
创建线程的方式有两种,可以通过Thread类直接创建,也可以通过实现Runnable接口,传递给Thread构造函数来创建。后者可以实现多个线程执行相同的run方法,Thread方法的实现是调用Runnable的run方法。
3、线程的常用方法
3.1 start
启动线程,调用该方法后,CPU才能够开始调度该线程,但不是不一定马上调度到,还需要看CPU具体的执行情况。
Thread t = new Thread();
t.start();
3.2 run
我们需要实现的方法,在方法中执行具体的业务逻辑代码。应用程序不需要调用该方法,而是CPU在调度该线程执行后,会自动调用该方法。
3.3 yield
暂时放弃cpu的抢占权,但是瞬间即逝,即该方法执行后又立即开始抢占CPU开始执行。因此是一个瞬间的动作。这通常是一个暗示,不能完全保证其达到目的。
//当前线程放弃CPU抢占权
Thread.yield();
3.4 join
等待指定的线程执行完成后,当前线程才能继续执行。因此也可以理解为将指定的线程执行过程加入到当前的线程中。
Thread t = new Thread() ;
//等待t执行完之后当前线程继续执行
t.join() ;
3.5 sleep
休眠指定的时间片(毫秒值),就是让当前线程休眠一段时间,时间一到,也不一定就会立即继续执行,还需要等待CPU的调度执行时间。
//当前线程休眠1s
Thread.sleep(1000) ;
3.6 守护线程
守护线程是通常是为那些非守护线程提供服务的。如果一个进程中剩余的线程都是守护线程,则进程结束。
Thread t = new Thread();
//设置线程t为守护线程
t.setDaemon(true) ;
3.7 holdsLock
判断当前线程是否持有指定的对象的锁,该方法是静态方法。
//创建锁旗标
Object lock = new Object();
//判断当前线程是否持有lock的锁
Thread.holdsLock(lock) ;
4、线程安全问题
线程安全问题是多线程编程中必然会遇到的问题,通常是由于多个线程并发访问共享变量,导致变量的内容不一致引发的安全问题。解决方式就是上锁,即使用synchronized关键字。java中任何对象都含有锁旗标,都可以作为锁出现,因此也相当于信号灯方式,同一时刻,只能有一个线程可以对该锁旗标上锁,其他线程会处于blocked状态,即阻塞状态。线程解锁后,其他线程可以进行抢夺,再进行上锁。锁操作过程中,需要确保的是线程是对同一个对象上锁。实现锁方式有两种,同步方法和同步代码块。
//非静态方法是对当前对象上锁,即this对象
public synchronized void m(){
...
}
//静态方法是以当前Class描述符为锁
public static synchronized void m(){
...
}
//
public void m(){
//同步代码块使用指定对象作为锁
synchronized(lock){
...
}
}
5、生产消费问题
同步解决了线程安全问题的同时,也带来了生产消费问题。所谓生产消费问题是生产者生产产品,消费者消费产品,生产的速率高于消费的速率,导致仓库最终会溢出,称这类现象为生产消费问题。解决方法就是使用等待唤醒机制放置仓库溢出。即生产者发现仓库已满,进进入等待队列,消费发现没有产品,也进入等待队列,两者均在有动作执行候,发送通知,通知等待队列中的线程继续开始执行。
/**
* 生产者生产过程,判断池中是否已满
*/
public synchronized void put(Integer x){
while(pool.isFull()){
this.wait() ;
}
pool.put(x) ;
this.notify() ;
}
/**
* 消费者消费过程,判断池中是否已空
*/
public synchronized void put(Integer x){
while(pool.isEmpty()){
this.wait() ;
}
pool.remove() ;
this.notify() ;
}
6、死锁问题
如果所有线程都进入等待队列,都等着别人发送通知,但是没有人能够发通知的时候,此时程序处于一种死锁状态。如下经过精心设计的程序就会导致死锁。程序最终的结果是生产者和消费者都进入等待队列。
class PCDemo5{
public static void main(String[] args){
//使用java中集合类,List是列表。
Pool pool = new Pool();
Productor p1 = new Productor("生产者1",pool);
p1.setName("p1");
Consumer c1 = new Consumer("消费者",pool);
c1.setName("c1");
Consumer c2 = new Consumer("消费者",pool);
c2.setName("c2");
p1.start();
c1.start();
c2.start();
}
}
//生产者
class Productor extends Thread{
static int i = 0 ;
private String name ;
private Pool pool ;
public Productor(String name ,Pool pool){
this.name = name ;
this.pool = pool ;
}
public void run(){
while(true){
pool.add(i ++);
}
}
}
//消费者
class Consumer extends Thread{
private String name ;
private Pool pool ;
public Consumer(String name ,Pool pool){
this.name = name ;
this.pool = pool;
}
public void run(){
while(true){
pool.remove();
//System.out.println("-: " + i);
}
}
}
class Pool{
private java.util.List<Integer> list = new java.util.ArrayList<Integer>();
//容器最大值
private int MAX = 1 ;
//添加元素
public void add(int n){
synchronized(this){
try{
String name = Thread.currentThread().getName();
while(list.size() == MAX){
System.out.println(name + ".wait()");
this.wait();
}
list.add(n);
System.out.println(name + " + : " + n);
System.out.println(name + ".notify()");
this.notify();
}catch(Exception e){
e.printStackTrace();
}
}
}
//删除元素
public int remove(){
synchronized(this){
try{
String name = Thread.currentThread().getName();
while(list.size() == 0){
System.out.println(name + ".wait()");
this.wait();
}
int i = list.remove(0);
System.out.println(name + " - : " + i);
System.out.println(name + ".notify()");
this.notify();
return i ;
}
catch(Exception e){
e.printStackTrace();
}
return -1 ;
}
}
}
7、线程变换状态图
8、思考题
8.1熊吃蜂蜜问题
两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光。
/**
* 罐子类,容器
*/
class Box{
//最大量
public static int MAX = 50 ;
//当前蜂蜜量
private int honeyNum = 0 ;
//向罐子追加蜂蜜
public synchronized void add(int n){
while(honeyNum == MAX){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
honeyNum ++ ;
this.notify();
}
/**
* 消费行为
*/
public synchronized int clearAll(){
while(honeyNum < Bear.MIN){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int n = honeyNum ;
honeyNum = 0 ;
notify();
return n ;
}
}
/**
* 熊
*/
class Bear extends Thread {
public static int MIN = 20 ;
private String bearName ;
private Box box ;
public Bear(Box box, String bearName){
this.box = box ;
this.bearName = bearName ;
}
public void run() {
for(;;){
int n = box.clearAll();
System.out.println(bearName + " : " + n);
}
}
}
/**
* 蜜蜂
*/
class Bee extends Thread {
private String bname;
private Box box ;
public Bee(Box box, String bname) {
this.box =box;
this.bname = bname;
}
public void run() {
int index = 1 ;
for(;;){
box.add(index);
System.out.println(bname + " : " + index);
index ++ ;
}
}
}
//测试类
class App{
public static void main(String[] args) {
Box box = new Box() ;
new Bear(box, "xxxxxx1").start();
new Bear(box, "xxxxxx2").start();
for(int i = 0 ; i < 30 ; i ++){
new Bee(box , "B" + i).start();
}
}
}
8.2 和尚吃馒头问题
有30个和尚,100个馒头,每个和尚最多吃4馒头,最少一个馒头,一次只能吃一个馒头。满足上述条件下,尽快把馒头吃了。
/**
* 派发馒头的类
*/
class Boss{
//剩余的馒头数
public static int breadNum = 30 ;
//未吃馒头的和尚数
public static int uneatedMonks = 10 ;
//获取馒头
public synchronized int getBread(Monk monk){
//不足最小值
if(monk.eated < Monk.MIN){
//取出最上方的馒头
int tmp = breadNum ;
breadNum -- ;
if(monk.eated == 0){
uneatedMonks -- ;
}
return tmp ;
}
if(monk.eated == Monk.MAX){
return 0 ;
}
//判断是否有多余的馒头
if(breadNum > (uneatedMonks * Monk.MIN)){
int tmp = breadNum ;
breadNum -- ;
return tmp ;
}
return 0 ;
}
}
/**
* 和尚类
*/
class Monk extends Thread{
private String mname ;
public static int MAX = 4 ;
public static int MIN = 2 ;
//已经吃了多少个
private int eated ;
private String breadNumStr = "" ;
private Boss boss ;
public Monk(Boss boss , String mname){
this.boss = boss ;
this.mname = mname ;
}
public void run() {
for(;;){
int breadNo = boss.getBread(this) ;
if(breadNo == 0){
System.out.printf("%s吃了%d:(%s)
" , mname , eated , breadNumStr);
break ;
}
else{
breadNumStr = breadNumStr + "," + breadNo ;
eated ++ ;
}
}
}
}
//测试类
class App{
public static void main(String[] args) {
Boss boss = new Boss();
for(int i = 0 ; i < 10 ; i ++){
new Monk(boss , "tom" + i).start(); ;
}
}
}