进程:即任务
任务并发执行是一个宏观概念,微观上是串行的
进程的调度是由OS负责的(有的系统为独占式,有的系统为共享式,根据重要性,进程有优先级)
线程由两种方式实现:
一:
Class MyThread extends Thread{
public void run(){
需要进行执行的代码,如循环
}
}
public class TestThread{
main(){
Thread t1=new MyThread();
t1.start();
}
}
二:
Class MyThread implement Runnable{
Public void run(){
Runnable target=new MyThread();
Thread t2=new Thread(target);
Thread.start();//启动线程
}
}
下面为线程中的7个非常重要的状态:
注意:图中标记:
①输入完毕;②wake up③t1退出
(1)如等待输入(输入设备进行处理,而CPU不处理),则放入阻塞,直到输入完毕。
(2)线程休眠sleep()
(3)t1.join()指停止main(),然后在某段事件内将t1加入运行队列,直到t1退出,main()才结束
***①②③与(1)(2)(3)是一一对应的
进程的休眠sleep:当main()运行完毕,即使在结束时间片还没有用完,CPU也放弃此时间片,继续运行其他程序
t1.join()表示运行线程放弃执行权,进入阻塞状态。
当t1结束时,main()可以重新进入运行状态。
t1.join()实际上是把并发的线程编程并发运行
线程的优先级:1-10,数值越大优先级越高,优先级越高被OS选中的可能性就越大。(不建议使用,不同操作系统的优先级并不相同)
一个使用yield()就马上交出执行权,回到可运行状态,等待OS的再次调用
程序员需要关注的线程同步和互斥的问题:
多线程的并发一般不是由程序员决定,而是由容器决定。
多线程出现故障的原因:
两个线程同时访问一个数据资源(临界资源),形成数据发生不一致和不完整。
数据不一致往往是因为一个线程中的两个关联的操作只完成了一步。
为避免上述问题可对数据进行加锁:
每个对象除了属性和方法外,都有一个monitor(互斥锁标记),用来将一个对象交给一个线程,只有拿到monitor的线程才能够访问在个对象。
Synchronized:这个修饰词可以用来修饰方法和代码块,并且其一定是使用在一个方法中。
锁标记是对象的概念,加锁是对对象加锁,目的是在线程之间进行协调
当用Synchronized修饰某个方法时,表示该方法对当前对象加锁。给方法加Synchronized和用Synchronized修饰对象的效果是一致的
一个线程可以拿到多个锁标记,一个对象最多只能将monitor给一个线程。Synchronized是以牺牲程序运行的效率为代价的,因此应尽量控制互斥代码块的范围。
线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池。
锁标记如果过多,就会出现线程等待其他线程释放锁标记,而又不释放自己的锁标记供其他线程运行的状况,就是死锁。
死锁问题通过线程间的通信的方式进行解决,实际上也就是协调机制。
Object类中的wait(),在运行过程中,线程调用wait(),此时表示线程将释放自己所有的锁标记,同时进入这个对象的等待队列。
等待队列的状态也是阻塞队列,只不过线程释放自己的锁标记。
Notify():如果一个线程调用对象的notify(),就是通知等待队列的一个线程出列。进入锁池,如果使用notifyall()则通知等待队列中的所有线程出列
***只能对加锁的资源进行wait()和notify()
***释放锁标记只有在Synchronized代码结束或调用wait(),锁标记是自己不会自动释放,必须有通知。
***在程序中判定一个条件是否成立时要注意使用While比使用If严密
多线程中的重点:实现多线程的两种方式,Synchronized,以及生产者和消费者问题