最近打算去一家电商公司,对于高并发的数据访问控制有着严格的要求,近期打算把多线程的知识在好好补一下。
线程调度有五个状态;
开始,可运行,运行,阻塞,死亡。
启动线程有两种方法。继承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

很好,问题得到了解决,交替执行并保证了数据的正确性。
看了此篇文章是不是感觉收获蛮大