1,为什么需要线程?
作用:提升cpu的利用率,如,早期的dos系统,执行2个命令时( command 1, command 2 ),如果command1【假如是磁盘遍历文件的IO操作】执行的时间比较长,那么command 2必须等待,这种方式就是同步阻塞,
cpu就闲置了,为了提高cpu的利用率,我们就要使用多线程,如果一个任务时间比较长,cpu就暂时挂起他,去执行另外的线程,所以线程一般是异步的。
2,每一个进程至少会有一个线程在运行
public classTest {public static voidmain(String[] args) {//打印线程的名称
System.out.println( Thread.currentThread().getName() );
}
}
输出结果为 "main" ,注意这个main是线程的名字,跟main函数的名字相同而已。
3,在java中实现多线程有2种方式
>继承Thread类
>实现Runnable接口
在run方法中写线程要执行的任务
class MyThread extendsThread{public voidrun(){
System.out.println("MyThread::run");
}
}public classThreadUse1 {public static voidmain(String[] args) {
MyThread mt= newMyThread();
mt.start();
System.out.println("运行结束");
}
}
从运行结果可知,run方法是在之后执行的,虽然start开启线程比 【System.out.println( "运行结束" );】 他早,这说明,CPU在调用线程的时候,是随机的
4,再次验证cpu调用线程的随机性
class MyThreadRand extendsThread{public voidrun(){try{for ( int i = 0; i < 10; i++) {int time = ( int )( Math.random() * 1000);
Thread.sleep( time );
System.out.println("MyThread:" +Thread.currentThread().getName() );
}
}catch( InterruptedException e ){
e.printStackTrace();
}
}
}public classRandThread {public static voidmain(String[] args) {try{
MyThreadRand mt= newMyThreadRand();
mt.setName("自定义线程");
mt.start();for ( int i = 0; i < 10; i++) {int time = ( int )( Math.random() * 1000);
Thread.sleep( time );
System.out.println("MainThread:" +Thread.currentThread().getName() );
}
}catch( InterruptedException e ){
e.printStackTrace();
}
}
}
从执行结果可知,线程的调度没有什么规律,是随机的, 这里补充一点,start方法作用是通知 “线程规划器”,这个线程已经准备好了,等待调用线程的run方法,就是让系统安排一个时间来调用run方法。如果直接调用run方法,线程就变成同步方式了,必须等待MyThreadRand的run方法执行完成之后,才会执行main函数中的线程
5,start方法的顺序,不代表线程的启动顺序
class MyThreadStart extendsThread{private inti;public MyThreadStart( inti ) {this.i =i;
}public voidrun(){
System.out.println( i );
}
}public classRandThread2 {public static voidmain(String[] args) {
MyThreadStart s1= new MyThreadStart( 1);
MyThreadStart s2= new MyThreadStart( 2);
MyThreadStart s3= new MyThreadStart( 3);
MyThreadStart s4= new MyThreadStart( 4);
MyThreadStart s5= new MyThreadStart( 5);
MyThreadStart s6= new MyThreadStart( 6);
MyThreadStart s7= new MyThreadStart( 7);
MyThreadStart s8= new MyThreadStart( 8);
MyThreadStart s9= new MyThreadStart( 9);
MyThreadStart s10= new MyThreadStart( 10);
s1.start();
s2.start();
s3.start();
s4.start();
s5.start();
s6.start();
s7.start();
s8.start();
s9.start();
s10.start();
}
}
6,实现Runnable接口
class MyThreadRunnable implementsRunnable {public voidrun(){
System.out.println( Thread.currentThread().getName() );
}
}public classThreadRunnable {public static voidmain(String[] args) {
MyThreadRunnable mt= newMyThreadRunnable();
Thread t= newThread( mt );
t.setName("自定义线程1");
t.start();
}
}
那么两种多线程的实现方式,有什么不同呢?
>继承Thread类
>实现Runnable接口
1,使用继承Thread类的方式,多线程之间的数据不共享
class MyThreadShare extendsThread{private int count = 5;publicMyThreadShare( String name ){this.setName( name );
}public voidrun(){while( count-- > 0){
System.out.println( Thread.currentThread().getName()+ "->" +count );
}
}
}public classThreadShare {public static voidmain(String[] args) {
MyThreadShare mt1= new MyThreadShare( "A");
MyThreadShare mt2= new MyThreadShare( "B");
MyThreadShare mt3= new MyThreadShare( "C");
mt1.start();
mt2.start();
mt3.start();
}
}
2,而要想实现线程之间的数据共享,我们可以改一下
备注:线程数据共享与不共享,都有对应的场景,比如火车站4个窗口卖票,很显然需要线程共享数据。如:总共用10张票,如果窗口卖了1张,其他窗口就指剩下9张,这才是比较贴近实际的,如果用第一种方式,相当于有40张余票了。
class MyThreadShare2 extendsThread{private int count = 5;public voidrun(){while( count-- > 0){
System.out.println( Thread.currentThread().getName()+ "->" +count );
}
}
}public classThreadShare2 {public static voidmain(String[] args) {
MyThreadShare2 mt= newMyThreadShare2();
Thread ta= new Thread( mt, "A");
Thread tb= new Thread( mt, "B");
Thread tc= new Thread( mt, "C");
ta.start();
tb.start();
tc.start();
}
}
从结果上看,好像实现了,数据共享,但是有点异常,B->3 很明显不对,这种现象,在多线程编程里面,叫“线程非安全”。现实生活中也有类似场景,比如4S店卖车,两个客户同时预订了这辆车。那估计少不了一番辩论。怎么解决这个问题呢?一般来说,在客户订车之前,销售员要先查看库存,如果客户下单,要把库存占用。表明有人预订,其他销售员看见了,就知道车被预订了。程序中也是类似。如果要访问这个变量,我们就给他加锁,类似于销售员占用库存。在方法前加上synchronized关键字。那么其他线程访问的时候,必须拿到这把锁,才能访问。synchronized可以在任意对象或者方法上加锁。
class MyThreadShare2 extendsThread{private int count = 5;//public void run(){//产生线程非安全问题
synchronized public voidrun(){while( count-- > 0){
System.out.println( Thread.currentThread().getName()+ "->" +count );
}
}
}public classThreadShare2 {public static voidmain(String[] args) {
MyThreadShare2 mt= newMyThreadShare2();
Thread ta= new Thread( mt, "A");
Thread tb= new Thread( mt, "B");
Thread tc= new Thread( mt, "C");
ta.start();
tb.start();
tc.start();
}
}
3,模拟用户登录场景,如果有两个用户登录,我们让其中一个用户线程占时挂起。看下会出现什么情况
classLogin {private staticString userName;private staticString userPwd;public static voiddoPost( String _userName, String _userPwd ){try{
userName=_userName;if( userName.equals( "ghostwu") ) {
Thread.sleep(3000);
}
userPwd=_userPwd;
System.out.println( userName+ "---->" +userPwd );
}catch( InterruptedException e ){
e.printStackTrace();
}
}
}class ThreadA extendsThread{public voidrun(){
Login.doPost("ghostwu", "abc123");
}
}class ThreadB extendsThread{public voidrun(){
Login.doPost("ghostwuB", "abc1234");
}
}public classUserLogin {public static voidmain(String[] args) {
ThreadA ta= newThreadA();
ThreadB tb= newThreadB();
ta.start();
tb.start();
}
}
在A线程挂起的时候,他之前的赋值已经被B线程改变了,所以结果与预想的ghostwu abc123不同。很明显,我们要上锁。
synchronized public static voiddoPost( String _userName, String _userPwd ){try{
userName=_userName;if( userName.equals( "ghostwu") ) {
Thread.sleep(3000);
}
userPwd=_userPwd;
System.out.println( userName+ "---->" +userPwd );
}catch( InterruptedException e ){
e.printStackTrace();
}
}