Java中有一个比较重要的知识点是多线程,废话就不说了,直接进入多线程的知识点。对多线程我们需要了解和知道一下的知识点:多线程的实现,多线程的安全性,多线程的线程通信,守护线程和线程的优先级等知识点。
首先,我想写点我对多线程的一点认识,其中说的语言不会很官方,但是我相信里面绝对没有坑,不会把大家带到歧途上去的。首先多线程的出现是为了更好的利用计算机的资源,提高程序的响应速度。多线程的运行由操作系统来控制,多核和单核的cpu执行同一个多线程程序的过程可能会不同,在单核中多线程程序只能在单核之间切换执行,在一个时间点上只能有一个线程处于运行的状态。但是在多核的cpu上我们会看到,同一个时间点上,多线程的程序的几个线程可能会运行在cpu的不同核上面,也就是说在同一个时间点上有多个线程同时在运行。大家这里注意点啊,平时我们说的多线程的同时运行很多的时候指的是多个线程在cpu上做快速的切换,如果是单核的cpu这个时候只是多个线程在做快速的切换,在任意一个时间点上,只有一个线程在运行,但是对于多核的cpu可能会是上面单核的情况也可能会是这样的状态:多个线程运行在不同的核心上,这个时候同一个时间点上会有多个线程在运行。
接下来我们看下关于多线程的实现,有二种方法,继承Thread 类和实现Runnable 接口,由于Java是单继承的,所以在平时的开发中我们使用的最多是实现Runnable接口来做的,具体怎么实现,自己去网上找啊。
接下来我们看看多线程的线程安全性的一点问题。为什么会出现线程的安全性那?出现这个问题的原因就是我们在多个线程之间共享和操作和同一个数据,如果不操作同一个数据,各个线程各自操作自己独立的数据,那么就不会有这样的问题出现,既然要共享和操作同一个数据,由于一个线程在执行的时候,会失去cpu的执行资格,等它再获取cpu的执行资格的时候会接着上次执行的继续执行,但是在它没有执行的这段时间会有其他的线程会操作共享的数据,这样可能就会出现错误,下面对这个做一个小的例子程序给大家看下,很简单,有经验的就不用看了。
private int total_number = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(total_number>0)
{
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(total_number--);
}
}
加上 sleep()只是让问题凸显出现,如果不加这个程序本身也是有问题的,会在某次运行的时候出现,但是程序员在自己电脑上运行的时候可能半天不会出现,这段本身就是有问题的。打印的结果中出现0,-1,-2,为什么会出现真的问题那,我们可以这样看,当我们假设线程一运行到判断和total_number>0 的时候,total_number这个时候是1,刚进去这个时候线程一sleep了,这个时候线程二进来了它也会进行判断,这个时候total_number 还是1,它判断之后也进入sleep ,这个时候我们可以假设线程一sleep的时间到了,并且获得了cpu的执行资格,这个时候我们会看到线程一会接下来执行打印的操作,当然就打印出了-1的错误数据。
出现了错误的数据,我们应该怎么办啊,Java为我们提供的解决办法是使用锁synchronized,使用锁之后我们可以让一个线程使用的的资源在没有完全释放的时候,其他线程无法进行访问,上面的代码可以改成:
private int total_number = 100;
Object Ob = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (Ob) {
while(total_number>0)
{
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(total_number--);
}
}
}
在这里我们使用了Object对象的锁。关于这里锁的使用,我们可以任意的选择。
在使用锁的时候使用的时候最常出现的一个问题就是死锁问题,死锁的出现就是二个线程都想访问对方的资源但是占着自己的资源不放,这样会造成相互的等待,就出现了死锁,解决这个问题的的办法就是,每一个线程都把自己需要访问的资源都给加上锁,这样就不会出现死锁的问题了。
我们可以把锁加在一段代码块上,同样我们可以把锁加在函数上,加在静态函数的锁使用的是该函数所在类的.class,而在普通函数的锁是this.比如下面的例子:
class A{
public static synchronized void method_a(){
}
public synchronized void method_b(){
}
}
上面的二个函数都加了锁,第一个函数是 static 的,所以它的锁是A.class,而函数method_b 是非静态的,它的锁是this.
接下来我们看线程之间的通信,所谓线程的通信就是指多个线程为了协调完成一个任务,这个任务由这多个线程之间协调完成,并且这个任务的几个部分之间有先后执行的顺序,这个时候我们就需要进行线程之间的通信了,一个线程的任务执行完成之后,通知其他的线程去做,下面是一个简单的例子代码,我们结合代码会更生动点啊。
/**
* 2016年6月21日21:36:18
* @author wukj
* 这是一个测试线程之间通信例子,在这个程序中我们模拟一个工厂生产物品和消费物品的过程,
* 在一个线程中生产物品,在另一线程中消费这个物品,这两个线程之间要通信,只有在生产的
* 线程生产完成了一个物品之后,消费的线程才可以消费掉这个物品。
*
*/
public class Resource {
private int res_id = 0; // 物品的id
boolean flag = true;// 标志位,是为了线程之间通信使用的
/**
* 这是商品生产的类,如果 flag 则进行商品的生产并且在生产之后将标志位 设成
* false,并且通知持有同一个锁的其他线程开始自己的线程执行。
*/
public synchronized void create() {
if (flag) {
System.out.println(++res_id+"is created");
flag = false;
this.notifyAll();
}else {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void sell(){
if(!flag){
System.out.println(res_id+"is sold");
flag = true;
this.notifyAll();
}else{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public int get_id(){
return res_id;
}
}
/**
* 2016年6月21日22:43:22
* @author wukj
* 这类是实现了 Runable 接口的 Create 类 主要用于多线程生产
*/
public class Create implements Runnable{
private Resource resource;
public Create(Resource resource) {
// TODO Auto-generated constructor stub
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (resource.get_id()<200) {
resource.create();
}
}
}
/**
* 2016年6月21日22:44:35
* @author wukj
* 这个类是实现了Runnable 的sell 类主要用于 对产品进行消费
*
*/
public class Sell implements Runnable {
private Resource resource;
public Sell(Resource resource){
this.resource = resource;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(resource.get_id()<201){
resource.sell();
}
}
}
public class MTest {
public static void main(String [] args){
Resource resource = new Resource();
Create create = new Create(resource);
Sell sell = new Sell(resource);
Thread t1 = new Thread(create);
Thread t2 = new Thread(sell);
Thread t3 = new Thread(create);
Thread t4 = new Thread(sell);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
在上面的代码中我们使用到wait(),notify(),和notifyAll(),三者是在线程通信的时候必须被使用到的,三者的配合可以很好的完成线程的通信,首先我们看下这三个API。
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。因为都个对像都有锁,锁是每个对像的基础,当然操作锁的方法也是最基础了。
wait():
等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。
调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。
notify():
唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
notifyAll():
唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。
这里我们可以看到执行wait和notify之后,该对象上的锁也被释放。我们可以进而思考这样的一个问题。我们可以结合下面的一个简单的代码辅助我们思考这个问题:
public synchronized void sold(){
if(....)
this.wait();
....;
....;
}
假设有一个线程 Thread_1 进入执行 sold 了,正好满足 if 的条件,会执行this.wait(),执行之后,Thread_1持有的锁会被释放掉,这个时候如果Thread_2也进入之后,也正好满足if 条件也会执行this.wait(),执行之后它也会释放掉锁。如果通过其他线程的notify 或者 notifyAll 唤醒了Thread_1 和Thread_2他们都会接着执行wait后面的代码。在这里我们会想到 sleep(),wait() 和sleep()的区别在于wait 会释放掉锁,而 sleep() 并不会释放掉锁。
有了上面的基础之后,我们简单分析下上面的线程通信的代码例子程序,上面的例子中我们使用了二个线程进行产品的生产,二个线程进行产品的消费,在线程进行生产或者消费之后都要唤醒等待的线程去执行操作。如果只有一个线程进行生产,一个线程进行消费,可以使用notify(),但是这里使用的是二个线程在进行生产和消费,所以要是notifyAll,因为有notify 只能每次唤醒其中的一个线程,在执行代码的时候,可能会出现四个线程都 wait 的情况。
接下来我们看看守护线程,感觉守护是不是有点难理解,换个说法后台线程,好理解了吧,当我们把一个线程设成守护线程之后,当所有的前台线程执行完成之后,这个线程自动会被jvm 杀死,把一个线程设成守护线程也很简单,就是下面的简单例子所示:
Thread t5 = new Thread();
t5.setDaemon(true);
参数是 true就是守护线程,false就不是。
上面的文章就是简单的介绍了多线程中使用的一些东西,算是自己的笔记,大家如果发现里面有什么问题可以和我取得联系,修改其中的问题,共同进步。