最近打算去一家电商公司,对于高并发的数据访问控制有着严格的要求,近期打算把多线程的知识在好好补一下。
线程调度有五个状态;
开始,可运行,运行,阻塞,死亡。
启动线程有两种方法。继承Thread类或则实现Runnable接口,其实Thread类也实现了Runnbale接口并实现了接口中唯一的方法run。但是Thread实例只能启动一次,无法共享变量,因此Runnable的灵活性更高。Thread(Runnable target)可以去执行Runnable实例的run方法。虽然start方法和run方法执行的内容一样但是本质是不同的,run方法只是单纯的调用而已并没有启动多线程,而start方法可以启动多线程。去查看jdk源码可以发现start有本地native修饰符,这会调用系统函数,优化执行效率。
可运行是一个线程池,对于JVM一般是从线程池里选择优先级高的线程抢占式执行,如果优先级都一样,那就看谁运气好抢到CPU资源。一般可以自己设置优先级0~10。这里还有用户线程和守护线程,JVM实例会在用户线程结束时退出,而不会等待守护线程。
运行态即开始执行该实例的run方法。为了减缓执行可以采用线程Thread.sleep(休眠的毫秒数)去给其他线程执行机会,但sleep不会放弃对象的锁,要放弃锁可以调用超类的wait(放弃毫秒数)方法。notify和notifyAll也可以去唤醒。注意,醒来并不一定马上得到执行的机会,而是放在可运行池里,让JVM去调度到运行态。在运行的程序也可以通过yield方法让本线程放到可运行池里,因此不要依赖这个方法,这个方法可能并没什么用。只作为一个参考手段。为了等待该线程执行结束,可以使用jion方法。
死亡态:死亡后不能复生。
接下来我来实现一个共享变量的控制实例:
创建一个我的对象,它有成员变量money,用两个线程去执行它,给一个消费金额不断的去消费,一直消费到0。
主线程中的测试:
public static void main(String args[]) throws InterruptedException
{
MyObject myObject = new MyObject(10);
//定义两个线程来共享这个myObject
Thread t1 = new Thread(myObject,"Thread-----A");
Thread t2 = new Thread(myObject,"Thread-----B");
t1.start();
t2.start();
//这里要用join方法,否则主线程在t1,t2执行的过程中可能并行执行,然后返回最后剩下的money,我想要显示的最终的结果,因此使用join方法是使t1,t2结束。
t1.join();
t2.join();
System.out.println("============主线程休眠1秒");
try
{
Thread.sleep(1000);
Thread.yield();//将主线程放到线程池,这里不起作用。t1,t2已经死亡
}
catch(Exception e)
{
e.printStackTrace();
}
//判断最后的共享变量money的值
System.out.println("myObject当中Money最终为:"+myObject.money);
}
接下来我们来共享变量的对象,如果没用控制方法同步
class MyObject implements Runnable
{
//可以对成员变量加锁
// volatile int money = 100;
int money = 10000;
//消费金额,用构造函数去初始化
private int consume;
//如果这里不加synchronized方法去控制
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while(money > 0)
{
System.out.println(Thread.currentThread().getName()+"执行操作"+ (i+1) +"次");
money = getSumMoney();
System.out.println("money:"+money);
i ++;
}
}
private int getSumMoney()
{
return this.money - consume;
}
//构造方法,消费
MyObject(int consume)
{
this.consume = consume;
}
public int getMoney() {
return money;
}
}
运行结果:
//省略前面
...
..
.
Thread-----A执行操作810次
money:100
Thread-----A执行操作811次
money:90
Thread-----A执行操作812次
money:80
Thread-----A执行操作813次
money:70
Thread-----A执行操作814次
money:60
Thread-----A执行操作815次
money:50
Thread-----A执行操作816次
money:40
Thread-----A执行操作817次
money:30
Thread-----A执行操作818次
money:20
Thread-----A执行操作819次
money:10
Thread-----A执行操作820次
money:0
Thread-----B执行操作181次
money:-10
============主线程休眠1秒
myObject当中Money最终为:-10
最终为-10,因为最后结果出现了问题。
我们加上访问修饰符synchronized
运行结果:
//....
//..
//.
Thread-----A执行操作994次
money:60
Thread-----A执行操作995次
money:50
Thread-----A执行操作996次
money:40
Thread-----A执行操作997次
money:30
Thread-----A执行操作998次
money:20
Thread-----A执行操作999次
money:10
Thread-----A执行操作1000次
money:0
============主线程休眠1秒
myObject当中Money最终为:0
结果算是没问题了,但是这里为什么只有A方法得到了执行呢?我再来分析代码,发现是线程A占用了对象的锁一直没释放,让线程B没有机会执行。为了达到两个线程都能够执行的目的,再加上wait方法放弃锁。代码如下:
public synchronized void run() {
// TODO Auto-generated method stub
int i = 0;
while(money > 0)
{
System.out.println(Thread.currentThread().getName()+"执行操作"+ (i+1) +"次");
money = getSumMoney();
System.out.println("money:"+money);
try
{
System.out.println(Thread.currentThread().getName() + "本线程放弃锁");
wait(50);
}
catch(Exception e)
{
e.printStackTrace();
}
i ++;
}
}
运行结果:
//....
...
..
.Thread-----A本线程放弃锁
Thread-----B执行操作496次
money:90
Thread-----B本线程放弃锁
Thread-----A执行操作496次
money:80
Thread-----A本线程放弃锁
Thread-----B执行操作497次
money:70
Thread-----B本线程放弃锁
Thread-----A执行操作497次
money:60
Thread-----A本线程放弃锁
Thread-----B执行操作498次
money:50
Thread-----B本线程放弃锁
Thread-----A执行操作498次
money:40
Thread-----A本线程放弃锁
Thread-----B执行操作499次
money:30
Thread-----B本线程放弃锁
Thread-----A执行操作499次
money:20
Thread-----A本线程放弃锁
Thread-----B执行操作500次
money:10
Thread-----B本线程放弃锁
Thread-----A执行操作500次
money:0
Thread-----A本线程放弃锁
============主线程休眠1秒
myObject当中Money最终为:0
很好,问题得到了解决,交替执行并保证了数据的正确性。
看了此篇文章是不是感觉收获蛮大