Java多线程初步--总结
1、线程简介
os当中的多线程分为用户级线程和内核级线程。
①用户级线程,由对应的进程创建,一个进程可以创建一个或多个线程,当进程获得处理机时,这些线程并发的执行,将这个时间片分配为更小的时间片,进而并发的执行。用户级线程对于系统内核是透明的,内核只能看到进程的执行,而不知道进程进一步分为了多个线程。也正是如此用户级线程无法享受到多核的好处。如果内核是单线程的,那么任何一个用户级线程若执行阻塞系统调用就会引起整个进程阻塞,即使还有其他线程可以在应用程序内运行。
②内核级线程,内核级线程对于系统内核是可见的,有系统内核调度各个线程,内核级线程可以享受到多核的好处。但是创建比较耗费资源。、
在java当中实现的是用户级线程。java中涉及到线程的一个接口是Runnable,实现了这个接口的类是Thread。Runnable当中只有一个run()方法,而在Thread类当中除了实现了run()方法外还实现了更多的方法,其中比较重要的方法就是start()方法。
start()方法和run()方法的主要区别就是run()方式就是执行线程,而start()方法是开启线程,其中包括了执行线程,但是在执行线程之前,还要做其他的工作。
注意:如果要实现线程的并发执行,不要直接调用run()方法,而要调用start()方法。这样才能保证,不同的线程并发的执行。而run()方法当中的内容便是线程执行时所执行的内容。
2、Thread类
在java当中Thread类共有八个构造函数,如下:
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name)
分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target)
分配新的 Thread 对象。
Thread(ThreadGroup group, Runnable target,String name)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的name 作为其名称,并作为 group 所引用的线程组的一员。
Thread(ThreadGroup group, Runnable target,String name, long stackSize)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈尺寸。
Thread(ThreadGroup group, String name)
分配新的 Thread 对象。
从这八个构造函数,可以看出Thread方法和Runnable接口是密切关联的。其中Runnable target是实现了Runnable接口的类或者是接口,而这个接口当中复写了run()方法,因此Runnabletarget在这里的主要的作用是使用复写了的run()方法。
String name,参数主要用于设置线程的名称。如果不对线程的名称进行显示的设置,线程的名称为Thread-N.其中N为不重复的整数。而设置线程的名称主要有两种方式,一种便是在构造函数当中设置,另外一种便是在start()方法之前使用setName()方式进行设置。
ThreadGroup group主要用于设置线程组。如果不进行显示的设置,默认所有的线程都再一个组当中。
long satckSize线程栈的大小。这个值一般为cpu页面值大小的整数倍。每个线程都有各自的线程栈,用户存放运行当中的变量。
3、通过Runnable接口创建线程
通过Runnable接口创建线程主要有两个步骤组成
①将实现了Runnable接口的类进行实例化
②建立Thread对象,并把第一步当中实例化的类做为Thread构造函数的参数。
这个过程可以从Thread类的构造方法当中看出来。
4、线程状态
与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中和这四种状态相关的方法。
- // 开始线程
- public void start( );
- public void run( );
- // 挂起和唤醒线程
- public void resume( ); // 不建议使用
- public void suspend( ); // 不建议使用
- public static void sleep(long millis);
- public static void sleep(long millis, int nanos);
- // 终止线程
- public void stop( ); // 不建议使用
- public void interrupt( );
- // 得到线程状态
- public boolean isAlive( );
- public boolean isInterrupted( );
- public static boolean interrupted( );
- // join方法
- public void join( ) throws InterruptedException;
关于为什么这三个方法不建议使用是因为在jdk当中已经将这三个方法标注为过期,也就是说在以后的jdk当中有可能取消这些方法。
5、join方法的理解
join方法的功能就是使异步执行的线程变成同步执行。就是说如果一个线程调用了join方法,那么这个线程必须执行完之后,其他线程成才能执行。也就是说,调用join方法的线程在run()方法执行完之后才能执行其他线程。
6、volatile关键字
volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。
在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
volatile关键字的原理是在java memery当中的main memery和working memory,中只放一份数据的备份。就是说只有在main memory当中放置。
7、向线程传递参数的三种方式
①在构造函数时传递参数,如Thread(Runnable target);
②通过变量和方法传递参数,如setName();
③通过回调函数传递参数,就是说在run()方法当中的再次调用函数。
以上三种方式可以归结为两类,一类是在thread.start()之前传递参数,对应于前两种。另一类就是在thread.start()方法之后,也就是最后一种方式。
8、线程返回参数的两种方式
①通过变量或者方法返回参数
②通过回调函数返回参数。
同时要注意,不要忽略一重要的知识点,main方法是主线程!!可以与其他用户自定义线程并发执行。
9、Synchronized关键字同步
①Synchronized关键字可以进行普通方法,静态方法,部分块,设置同步参数的方式进行同步。在使用synchronized块时应注意,synchronized块只能使用对象作为它的参数。
②如果在类中使用synchronized关键字来定义非静态方法,那将影响这个中的所有使用synchronized关键字定义的非静态方法。如果定义的是静态方法,那么将影响类中所有使用synchronized关键字定义的静态方法。这有点象数据表中的表锁,当修改一条记录时,系统就将整个表都锁住了,因此,大量使用这种同步方式会使程序的性能大幅度下降。
③synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法被调用的当前对象:synchronized(this)。在这种方式中,如果获得了synchronized块上的所,那么该对象其他的synchronized方法和临界区就不能被调用了。